67
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 $type\n"; // get file size $size = filesize($file); echo "File size is $size bytes\n"; // is file readable? echo is_readable($file) ? "File is readable\n" : "File is not readable\n"; // is file writable? echo is_writable($file) ? "File is writable\n" : "File is not writable\n"; // is file executable? echo is_executable($file) ? "File is executable\n" :

Files nts

  • View
    4.302

  • Download
    3

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Files nts

FROM PHP Programming Solutionspdf

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 PHPrsquos file_exists() functionltphp check to see if file exists result File existsecho file_exists(dummytxt) File exists File does not existgt

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 filersquos 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

ltphp set the file name$file = dummytxt 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 readableecho is_readable($file) File is readablen crarrFile is not readablen is file writableecho is_writable($file) File is writablen crarrFile is not writablen is file executableecho is_executable($file) File is executablen crarrFile is not executablengt

TIPIf PHPrsquos file functions donrsquot appear to be working as advertised try using the absolute file path to get to the file instead of a relative pathNOTESome 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

NOTEThe 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

ltphp set the file name$file = dummytxt read file contents into an array$dataArr = file($file)print_r($dataArr) read file contents into a string$dataStr = file_get_contents($file)echo $dataStrgt

If yoursquore 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

Herersquos howltphp define file to read$file = dummytxt open file$fp = fopen($file rb) or die (Cannot open file) read file contents into string$dataStr = fread($fp filesize($file))echo $dataStr close filefclose($fp) or die(Cannot close file)gt

NOTEIn case you were wondering the options passed to fopen() in the previous listing are used toopen the file in read-only mode (r) and binary mode (b)If yoursquore 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 ldquochunksrdquo with the fgets() function and then combine the chunks to create a complete string Herersquos an illustrationltphp open file$fp = fopen(mntnetmachine2hda1dummytxt rb)crarror die (Cannot open file) read contents into a stringwhile (feof($fp)) $dataStr = fgets($fp 1024) close filefclose($fp) or die (Cannot close file)

display contentsecho $dataStrgt

64 Reading Line Ranges from a FileProblemYou want to read a particular line or line range from a file

SolutionRead the file into an array with PHPrsquos file() function and then extract the required linesltphp read file into array$data = file(fortunestxt) or die(Cannot read file) get first lineecho $data[0] n get last lineecho end($data) n get line 5echo $data[4] n get lines 2-6$lines = array_slice($data 1 5)echo implode(n $lines)gt

Write a custom function that uses the fgets() and fseek() calls to pick one ormore lines out of a fileltphp function to get an arbitrary range of lines from a filefunction getLines($file $startLineNum $endLineNum) check for valid range endpointsif ($endLineNum lt $startLineNum) die(Ending line number must be greater than or crarrequal 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 linewhile (feof($fp) ampamp $lineCounter lt= $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 gt= $startLineNum ampamp $lineCounter lt= crarr$endLineNum) $lineData[] = $line close the filefclose($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 callerreturn $lineData return lines 2-6 of file as array$lines = getLines(fortunestxt 2 6)print_r($lines)

gt

CommentsExtracting one or more lines from a file is one of the more common problemsdevelopers face and itrsquos no surprise that there are so many creative solutions to itThe first listing outlines the simplest approach storing the lines of a file in an arraywith PHPrsquos file() function and then using array indexing to extract specific linesby numberThe second listing offers a more complicated approach wherein a customgetLines() function accepts three arguments a file path a starting line numberand an ending line number (for a single line the latter two will be equal) It theniterates through the named file incrementing a counter as each line is processedLines that fall within the supplied range will be saved to an array which is returnedto the caller once the entire file is processedYou can also get the first and last lines of a file with a combination of fseek()and fgets() function calls as illustrated hereltphp open file$fp = fopen(fortunestxt rb) or die(Cannot open file) get first linefseek($fp 0 SEEK_SET)echo fgets($fp) get last linefseek($fp 0 SEEK_SET)while (feof($fp)) $line = fgets($fp)echo $line close filefclose($fp) or die (Cannot close file)gt

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 193Here the fseek() function moves the internal file pointer to a specific locationin the file and the fgets() function retrieves all the data beginning from thepointer location until the next newline character To obtain the first line set the filepointer to position 0 and call fgets() once to obtain the last line keep callingfgets() until the end of the file is reached and the last return value will representthe last lineYou should also take a look at the listing in ldquo65 Reading Byte Ranges froma Filerdquo for a variant that extracts file contents by bytes instead of lines

65 Reading Byte Ranges from a FileProblemYou want to read a particular byte or byte range from a file

SolutionWrite a custom function encapsulating a combination of fseek() ftell() andfgetc() callsltphp function to get an arbitrary number of bytes from a filefunction getBytes($file $startByte $endByte) check for valid range endpointsif ($endByte lt $startByte) die(Ending byte number must be greater than or crarr

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 bytefseek ($fp $startByte SEEK_SET)while ((ftell($fp) gt $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 filefclose($fp) or die (Cannot close file) return data to callerreturn $data return first 10 bytes of fileecho getBytes(fortunestxt 0 9)gt

CommentsThe user-defined getBytes() function is similar to the getLines() functionillustrated in the first listing in ldquo64 Reading Line Ranges from a Filerdquo with theprimary difference lying in its use of fgetc() instead of fgets()The functionaccepts three arguments a file path a starting byte number and an ending bytenumber It then sets the internal file pointer to the starting byte value and loops overthe file character by character appending the result at each stage to a variable untilthe ending byte value is reached The variable containing the saved bytes is thenreturned to the caller as a string

66 Counting Lines Wordsand Characters in a FileProblemYou want to count the number of lines words and characters in a file

SolutionUse PHPrsquos file_get_contents() strlen() and str_word_count()functions to count words and characters in a fileltphp set file name and path$file = dummytxt

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 linesecho Counted sizeof($arr) line(s)n count characters with spaces$numCharsSpaces = strlen($str)echo Counted $numCharsSpaces character(s) with spacesn count characters without spaces$newStr = ereg_replace([[space]]+ $str)

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 2: Files nts

NOTEThe 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

ltphp set the file name$file = dummytxt read file contents into an array$dataArr = file($file)print_r($dataArr) read file contents into a string$dataStr = file_get_contents($file)echo $dataStrgt

If yoursquore 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

Herersquos howltphp define file to read$file = dummytxt open file$fp = fopen($file rb) or die (Cannot open file) read file contents into string$dataStr = fread($fp filesize($file))echo $dataStr close filefclose($fp) or die(Cannot close file)gt

NOTEIn case you were wondering the options passed to fopen() in the previous listing are used toopen the file in read-only mode (r) and binary mode (b)If yoursquore 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 ldquochunksrdquo with the fgets() function and then combine the chunks to create a complete string Herersquos an illustrationltphp open file$fp = fopen(mntnetmachine2hda1dummytxt rb)crarror die (Cannot open file) read contents into a stringwhile (feof($fp)) $dataStr = fgets($fp 1024) close filefclose($fp) or die (Cannot close file)

display contentsecho $dataStrgt

64 Reading Line Ranges from a FileProblemYou want to read a particular line or line range from a file

SolutionRead the file into an array with PHPrsquos file() function and then extract the required linesltphp read file into array$data = file(fortunestxt) or die(Cannot read file) get first lineecho $data[0] n get last lineecho end($data) n get line 5echo $data[4] n get lines 2-6$lines = array_slice($data 1 5)echo implode(n $lines)gt

Write a custom function that uses the fgets() and fseek() calls to pick one ormore lines out of a fileltphp function to get an arbitrary range of lines from a filefunction getLines($file $startLineNum $endLineNum) check for valid range endpointsif ($endLineNum lt $startLineNum) die(Ending line number must be greater than or crarrequal 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 linewhile (feof($fp) ampamp $lineCounter lt= $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 gt= $startLineNum ampamp $lineCounter lt= crarr$endLineNum) $lineData[] = $line close the filefclose($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 callerreturn $lineData return lines 2-6 of file as array$lines = getLines(fortunestxt 2 6)print_r($lines)

gt

CommentsExtracting one or more lines from a file is one of the more common problemsdevelopers face and itrsquos no surprise that there are so many creative solutions to itThe first listing outlines the simplest approach storing the lines of a file in an arraywith PHPrsquos file() function and then using array indexing to extract specific linesby numberThe second listing offers a more complicated approach wherein a customgetLines() function accepts three arguments a file path a starting line numberand an ending line number (for a single line the latter two will be equal) It theniterates through the named file incrementing a counter as each line is processedLines that fall within the supplied range will be saved to an array which is returnedto the caller once the entire file is processedYou can also get the first and last lines of a file with a combination of fseek()and fgets() function calls as illustrated hereltphp open file$fp = fopen(fortunestxt rb) or die(Cannot open file) get first linefseek($fp 0 SEEK_SET)echo fgets($fp) get last linefseek($fp 0 SEEK_SET)while (feof($fp)) $line = fgets($fp)echo $line close filefclose($fp) or die (Cannot close file)gt

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 193Here the fseek() function moves the internal file pointer to a specific locationin the file and the fgets() function retrieves all the data beginning from thepointer location until the next newline character To obtain the first line set the filepointer to position 0 and call fgets() once to obtain the last line keep callingfgets() until the end of the file is reached and the last return value will representthe last lineYou should also take a look at the listing in ldquo65 Reading Byte Ranges froma Filerdquo for a variant that extracts file contents by bytes instead of lines

65 Reading Byte Ranges from a FileProblemYou want to read a particular byte or byte range from a file

SolutionWrite a custom function encapsulating a combination of fseek() ftell() andfgetc() callsltphp function to get an arbitrary number of bytes from a filefunction getBytes($file $startByte $endByte) check for valid range endpointsif ($endByte lt $startByte) die(Ending byte number must be greater than or crarr

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 bytefseek ($fp $startByte SEEK_SET)while ((ftell($fp) gt $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 filefclose($fp) or die (Cannot close file) return data to callerreturn $data return first 10 bytes of fileecho getBytes(fortunestxt 0 9)gt

CommentsThe user-defined getBytes() function is similar to the getLines() functionillustrated in the first listing in ldquo64 Reading Line Ranges from a Filerdquo with theprimary difference lying in its use of fgetc() instead of fgets()The functionaccepts three arguments a file path a starting byte number and an ending bytenumber It then sets the internal file pointer to the starting byte value and loops overthe file character by character appending the result at each stage to a variable untilthe ending byte value is reached The variable containing the saved bytes is thenreturned to the caller as a string

66 Counting Lines Wordsand Characters in a FileProblemYou want to count the number of lines words and characters in a file

SolutionUse PHPrsquos file_get_contents() strlen() and str_word_count()functions to count words and characters in a fileltphp set file name and path$file = dummytxt

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 linesecho Counted sizeof($arr) line(s)n count characters with spaces$numCharsSpaces = strlen($str)echo Counted $numCharsSpaces character(s) with spacesn count characters without spaces$newStr = ereg_replace([[space]]+ $str)

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 3: Files nts

display contentsecho $dataStrgt

64 Reading Line Ranges from a FileProblemYou want to read a particular line or line range from a file

SolutionRead the file into an array with PHPrsquos file() function and then extract the required linesltphp read file into array$data = file(fortunestxt) or die(Cannot read file) get first lineecho $data[0] n get last lineecho end($data) n get line 5echo $data[4] n get lines 2-6$lines = array_slice($data 1 5)echo implode(n $lines)gt

Write a custom function that uses the fgets() and fseek() calls to pick one ormore lines out of a fileltphp function to get an arbitrary range of lines from a filefunction getLines($file $startLineNum $endLineNum) check for valid range endpointsif ($endLineNum lt $startLineNum) die(Ending line number must be greater than or crarrequal 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 linewhile (feof($fp) ampamp $lineCounter lt= $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 gt= $startLineNum ampamp $lineCounter lt= crarr$endLineNum) $lineData[] = $line close the filefclose($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 callerreturn $lineData return lines 2-6 of file as array$lines = getLines(fortunestxt 2 6)print_r($lines)

gt

CommentsExtracting one or more lines from a file is one of the more common problemsdevelopers face and itrsquos no surprise that there are so many creative solutions to itThe first listing outlines the simplest approach storing the lines of a file in an arraywith PHPrsquos file() function and then using array indexing to extract specific linesby numberThe second listing offers a more complicated approach wherein a customgetLines() function accepts three arguments a file path a starting line numberand an ending line number (for a single line the latter two will be equal) It theniterates through the named file incrementing a counter as each line is processedLines that fall within the supplied range will be saved to an array which is returnedto the caller once the entire file is processedYou can also get the first and last lines of a file with a combination of fseek()and fgets() function calls as illustrated hereltphp open file$fp = fopen(fortunestxt rb) or die(Cannot open file) get first linefseek($fp 0 SEEK_SET)echo fgets($fp) get last linefseek($fp 0 SEEK_SET)while (feof($fp)) $line = fgets($fp)echo $line close filefclose($fp) or die (Cannot close file)gt

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 193Here the fseek() function moves the internal file pointer to a specific locationin the file and the fgets() function retrieves all the data beginning from thepointer location until the next newline character To obtain the first line set the filepointer to position 0 and call fgets() once to obtain the last line keep callingfgets() until the end of the file is reached and the last return value will representthe last lineYou should also take a look at the listing in ldquo65 Reading Byte Ranges froma Filerdquo for a variant that extracts file contents by bytes instead of lines

65 Reading Byte Ranges from a FileProblemYou want to read a particular byte or byte range from a file

SolutionWrite a custom function encapsulating a combination of fseek() ftell() andfgetc() callsltphp function to get an arbitrary number of bytes from a filefunction getBytes($file $startByte $endByte) check for valid range endpointsif ($endByte lt $startByte) die(Ending byte number must be greater than or crarr

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 bytefseek ($fp $startByte SEEK_SET)while ((ftell($fp) gt $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 filefclose($fp) or die (Cannot close file) return data to callerreturn $data return first 10 bytes of fileecho getBytes(fortunestxt 0 9)gt

CommentsThe user-defined getBytes() function is similar to the getLines() functionillustrated in the first listing in ldquo64 Reading Line Ranges from a Filerdquo with theprimary difference lying in its use of fgetc() instead of fgets()The functionaccepts three arguments a file path a starting byte number and an ending bytenumber It then sets the internal file pointer to the starting byte value and loops overthe file character by character appending the result at each stage to a variable untilthe ending byte value is reached The variable containing the saved bytes is thenreturned to the caller as a string

66 Counting Lines Wordsand Characters in a FileProblemYou want to count the number of lines words and characters in a file

SolutionUse PHPrsquos file_get_contents() strlen() and str_word_count()functions to count words and characters in a fileltphp set file name and path$file = dummytxt

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 linesecho Counted sizeof($arr) line(s)n count characters with spaces$numCharsSpaces = strlen($str)echo Counted $numCharsSpaces character(s) with spacesn count characters without spaces$newStr = ereg_replace([[space]]+ $str)

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 4: Files nts

gt

CommentsExtracting one or more lines from a file is one of the more common problemsdevelopers face and itrsquos no surprise that there are so many creative solutions to itThe first listing outlines the simplest approach storing the lines of a file in an arraywith PHPrsquos file() function and then using array indexing to extract specific linesby numberThe second listing offers a more complicated approach wherein a customgetLines() function accepts three arguments a file path a starting line numberand an ending line number (for a single line the latter two will be equal) It theniterates through the named file incrementing a counter as each line is processedLines that fall within the supplied range will be saved to an array which is returnedto the caller once the entire file is processedYou can also get the first and last lines of a file with a combination of fseek()and fgets() function calls as illustrated hereltphp open file$fp = fopen(fortunestxt rb) or die(Cannot open file) get first linefseek($fp 0 SEEK_SET)echo fgets($fp) get last linefseek($fp 0 SEEK_SET)while (feof($fp)) $line = fgets($fp)echo $line close filefclose($fp) or die (Cannot close file)gt

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 193Here the fseek() function moves the internal file pointer to a specific locationin the file and the fgets() function retrieves all the data beginning from thepointer location until the next newline character To obtain the first line set the filepointer to position 0 and call fgets() once to obtain the last line keep callingfgets() until the end of the file is reached and the last return value will representthe last lineYou should also take a look at the listing in ldquo65 Reading Byte Ranges froma Filerdquo for a variant that extracts file contents by bytes instead of lines

65 Reading Byte Ranges from a FileProblemYou want to read a particular byte or byte range from a file

SolutionWrite a custom function encapsulating a combination of fseek() ftell() andfgetc() callsltphp function to get an arbitrary number of bytes from a filefunction getBytes($file $startByte $endByte) check for valid range endpointsif ($endByte lt $startByte) die(Ending byte number must be greater than or crarr

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 bytefseek ($fp $startByte SEEK_SET)while ((ftell($fp) gt $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 filefclose($fp) or die (Cannot close file) return data to callerreturn $data return first 10 bytes of fileecho getBytes(fortunestxt 0 9)gt

CommentsThe user-defined getBytes() function is similar to the getLines() functionillustrated in the first listing in ldquo64 Reading Line Ranges from a Filerdquo with theprimary difference lying in its use of fgetc() instead of fgets()The functionaccepts three arguments a file path a starting byte number and an ending bytenumber It then sets the internal file pointer to the starting byte value and loops overthe file character by character appending the result at each stage to a variable untilthe ending byte value is reached The variable containing the saved bytes is thenreturned to the caller as a string

66 Counting Lines Wordsand Characters in a FileProblemYou want to count the number of lines words and characters in a file

SolutionUse PHPrsquos file_get_contents() strlen() and str_word_count()functions to count words and characters in a fileltphp set file name and path$file = dummytxt

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 linesecho Counted sizeof($arr) line(s)n count characters with spaces$numCharsSpaces = strlen($str)echo Counted $numCharsSpaces character(s) with spacesn count characters without spaces$newStr = ereg_replace([[space]]+ $str)

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 5: Files nts

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 bytefseek ($fp $startByte SEEK_SET)while ((ftell($fp) gt $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 filefclose($fp) or die (Cannot close file) return data to callerreturn $data return first 10 bytes of fileecho getBytes(fortunestxt 0 9)gt

CommentsThe user-defined getBytes() function is similar to the getLines() functionillustrated in the first listing in ldquo64 Reading Line Ranges from a Filerdquo with theprimary difference lying in its use of fgetc() instead of fgets()The functionaccepts three arguments a file path a starting byte number and an ending bytenumber It then sets the internal file pointer to the starting byte value and loops overthe file character by character appending the result at each stage to a variable untilthe ending byte value is reached The variable containing the saved bytes is thenreturned to the caller as a string

66 Counting Lines Wordsand Characters in a FileProblemYou want to count the number of lines words and characters in a file

SolutionUse PHPrsquos file_get_contents() strlen() and str_word_count()functions to count words and characters in a fileltphp set file name and path$file = dummytxt

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 linesecho Counted sizeof($arr) line(s)n count characters with spaces$numCharsSpaces = strlen($str)echo Counted $numCharsSpaces character(s) with spacesn count characters without spaces$newStr = ereg_replace([[space]]+ $str)

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 6: Files nts

$numChars = strlen($newStr)echo Counted $numChars character(s) without spacesn count words$numWords = str_word_count($str)echo Counted $numWords wordsngt

CommentsItrsquos fairly easy to count the number of lines in a filemdashsimply read the file intoan array with file() which stores each line as an individual array element andthen count the total number of elements in the arrayCounting words and characters is a little more involved and requires you to firstread the file into a string with a function such as file_get_contents() Thenumber of words and characters (including spaces) can then be obtained by runningthe str_word_count() and strlen() functions on the stringTo obtain the number of characters excluding spaces simple remove all spacesfrom the string with ereg_replace() and then obtain the size of the string withstrlen() You can read more about how this works in the listing in ldquo14 RemovingWhitespace from Stringsrdquo and users whose PHP builds donrsquot support the relativelynewerstr_word_count() function will find an alternative way of counting wordsin the listing in ldquo113 Counting Words in a Stringrdquo196 P H P P r o g r a m m i n g S o l u t i o n s

67 Writing FilesProblemYou want to write a string to a file

SolutionUse the file_put_contents() functionltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players write string to filefile_put_contents(shakespearetxt $data) or die(Cannot write to crarrfile) echo File successfully writtengt

CommentsThe file_put_contents() function provides an easy way to write data to a fileThe file will be created if it does not already exist and overwritten if it does Thereturn value of the function is the number of bytes writtenTIPTo have file_put_contents() append to an existing file rather than overwrite itcompletely add the optional FILE_APPEND flag to the function call as its third argumentIf yoursquore using an older PHP build that lacks the file_put_contents()function you can use the fwrite() function to write to a file instead Herersquos howltphp define string to write$data = All the worlds a stagernAnd all the men and crarrwomen merely players open file

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 7: Files nts

$fp = fopen(shakespearetxt 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 fileif (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 filefclose($fp) or die (Cannot close file)gt

Here the fwrite() function is used to write a string to an open file pointer afterthe file has been opened for writing with fopen() and the w+ parameter Noticethe call to flock() before any data is written to the filemdashthis locks the file stopsother processes from writing to the file and thereby reduces the possibility of datacorruption Once the data has been successfully written the file is unlocked Lockingis discussed in greater detail in the listing in ldquo68 Locking and Unlocking FilesrdquoTIPTo have fwrite() append to an existing file rather than overwrite it completely change thefile mode to ab+ in the fopen() callNOTEThe flock() function is not supported on certain file systems such as the File AllocationTable (FAT) system and the Network File System (NFS) For an alternative file-locking solution forthese file systems look at the listing in ldquo68 Locking and Unlocking Filesrdquo and read more aboutflock() caveats at httpwwwphpnetflock

68 Locking and Unlocking FilesProblemYou want to lock a file before writing to it198 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse the flock() functionltphp open file$fp = fopen(dummytxt wb+) or die (Cannot open file) lock file write string to fileif (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 filefclose($fp) or die (Cannot close file)echo File successfully written

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 8: Files nts

gt

CommentsPHP implements both shared and exclusive file locks through its flock() functionwhich accepts a file pointer and a flag indicating the lock type (LOCK_EX forexclusive lock LOCK_SH for shared lock and LOCK_UN for unlock) Once a file islocked with flock() other processes attempting to write to the file have to waituntil the lock is released this reduces the possibility of multiple processes trying towrite to the same file simultaneously and corrupting itNOTEPHPrsquos file locking is advisory which means that it only works if all processes attempting to accessthe file respect PHPrsquos locks This may not always be true in the real worldmdashjust because a file islocked with PHP flock() doesnrsquot mean that it canrsquot be modified with an external text editorlike vimdashso itrsquos important to always try and ensure that the processes accessing a file use thesame type of locking and respect each otherrsquos locksSo long as your file is only written to by a PHP process flock() will usually suffice ifhowever your file is accessed by multiple processes or scripts in different languages it might beworth your time to create a customized locking system that can be understood and used by allaccessing programs The listing in this section contains some ideas to get you startedC 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 199On certain file systems the flock() function remains unsupported and willalways return false Users of older Windows versions are particularly prone to thisproblem as flock() does not work with the FAT file system If file locking is stilldesired on such systems it becomes necessary to simulate it by means of a userdefinedlockunlock API The following listing illustrates thisltphp function to set lock for filefunction lock($file) return touch($filelock) function to remove lock for filefunction unlock($file) return unlink ($filelock) function to check if lock existsfunction isLocked($file) clearstatcache()return file_exists($filelock) true false set file name$file = dummytxtwhile ($attemptCount lt= 60) if file is not in useif (isLocked($file)) lock filelock($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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 9: Files nts

$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 fileunlock($file)echo File successfully written break out of loopbreak else if file is in use increment attempt counter$attemptCount++ sleep for one secondsleep(1) try againgt

This simple locking API contains three functions one to lock a file one tounlock it and one to check the status of the lock A lock is signaled by the presenceof a lock file which serves as a semaphore this file is removed when the lock isreleasedThe PHP script first checks to see if a lock exists on the file by looking for thelock file If no lock exists the script obtains a lock and proceeds to make changesto the file unlocking it when itrsquos done If a lock exists the script waits one secondand then checks again to see if the previous lock has been released This check isperformed 60 times once every second at the end of it if the lock has still not beenreleased the script gives up and exitsNOTEIf flock() is not supported on your file system or if yoursquore looking for a locking mechanismthat can be used by both PHP and non-PHP scripts the previous listing provides a basic frameworkto get started Because the lock is implemented as a file on the system and all programminglanguages come with functions to test files it is fairly easy to port the API to other languages andcreate a locking system that is understood by all processes

69 Removing Lines from a FileProblemYou want to remove a line from a file given its line number

SolutionUse PHPrsquos file() function to read the file into an array remove the line and thenwrite the file back with the file_put_contents() functionltphp set the file name$file = fortunestxt read file into array$data = file($file) or die(Cannot read file) remove third lineunset ($data[2]) re-index array

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 10: Files nts

$data = array_values($data) write data back to filefile_put_contents($file implode($data)) or die(Cannot write to file)echo File successfully writtengt

CommentsThe simplest way to erase a line from a file is to read the file into an array removethe offending element and then write the array back to the file overwriting itsoriginal contents An important step in this process is the re-indexing of the arrayonce an element has been removed from itmdashomit this step and your output file willdisplay a blank line at the point of surgeryIf your PHP build doesnrsquot support the file_put_contents() function youcan accomplish the same result with a combination of fgets() and fwrite()Herersquos howltphp set the file name$file = fortunestxt 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 removedwhile (feof($fp)) $lineCounter++$line = fgets($fp)if ($lineCounter = $lineNum) $data = $line close the filefclose($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 itif (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 filefclose($fp) or die (Cannot close file)echo File successfully writtengt

The fgets() function reads the file line by line appending whatever it finds toa string A line counter keeps track of the lines being processed and takes care ofskipping over the line to be removed so that it never makes it into the data stringOnce the file has been completely processed and its contents have been stored in thestring (with the exception of the line to be removed) the file is closed and reopenedfor writing A lock secures access to the file and the fwrite() function then writesthe string back to the file erasing the original contents in the process

610 Processing Directories

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 11: Files nts

ProblemYou want to iteratively process all the files in a directory

SolutionUse PHPrsquos scandir() functionltphp define directory path$dir = test get directory contents as an array$fileList = scandir($dir) or die (Not a directory) print file names and sizesforeach ($fileList as $file) if (is_file($dir$file) ampamp $file = ampamp $file = ) echo $file filesize($dir$file) ngt

CommentsPHPrsquos scandir() function offers a simple solution to this problemmdashit returnsthe contents of a directory as an array which can then be processed using any loopconstruct or array functionAn alternative approach is to use the Iterators available as part of the StandardPHP Library (SPL) Iterators are ready-made extensible constructs designedspecifically to loop over item collections such as arrays and directories To processa directory use a DirectoryIterator as illustrated hereltphp 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-gtrewind() iterate over the directory using object methods print each file namewhile($iterator-gtvalid()) if ($iterator-gtisFile() ampamp $iterator-gtisDot()) print $iterator-gtgetFilename() crarr$iterator-gtgetSize() n$iterator-gtnext()gt

Here a DirectoryIterator object is initialized with a directory name and theobjectrsquos rewind() method is used to reset the internal pointer to the first entry inthe 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 thegetFilename() method while you can use the isDot() method to filter out theentries for the current () and parent () directories The next() method movesthe internal pointer forward to the next entryYou can read more about the DirectoryIterator at httpwwwphpnet~hellyphpextspl

611 Recursively Processing Directories

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 12: Files nts

ProblemYou want to process all the files in a directory and its subdirectories

SolutionWrite a recursive function to process the directory and its childrenltphp function to recursively process a directory and all its subdirectoriesfunction dirTraverse($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold file listglobal $fileList open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itdirTraverse($dir$file) else if this is a file do something with it for example reverse file namepath and add to array$fileList[] = strrev($dir$file) return the final list to the callerreturn $fileList recursively process a directory$result = dirTraverse(test)print_r($result)gt

CommentsAs illustrated in the listing in ldquo610 Processing Directoriesrdquo itrsquos fairly easy toprocess the contents of a single directory with the scandir() function Dealingwith a series of nested directories is somewhat more complex The previous listingillustrates the standard technique a recursive function that calls itself to travel everdeeper into the directory treeThe inner workings of the dirTraverse() function are fairly simple Everytime the function encounters a directory entry it checks to see if that value is a file ora directory If itrsquos a directory the function calls itself and repeats the process until itreaches the end of the directory tree If itrsquos a file the file is processedmdashthe previouslisting simply reverses the file name and adds it to an array but you can obviouslyreplace this with your own custom routinemdashand then the entire performance isrepeated for the next entryAnother option is to use the Iterators available as part of the Standard PHPLibrary (SPL) Iterators are ready-made extensible constructs designed specificallyto loop over item collections such as arrays and directories A predefined Recursive

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 13: Files nts

DirectoryIterator already exists and itrsquos not difficult to use this for recursive directoryprocessing Herersquos howltphp initialize an object pass it the directory to be processed$iterator = new RecursiveIteratorIterator( new crarrRecursiveDirectoryIterator(test) ) iterate over the directoryforeach ($iterator as $key=gt$value) print strrev($key) ngt

The process of traversing a series of nested directories is significantly simplerwith the SPL at hand First initialize a RecursiveDirectoryIterator object and pass itthe path to the top-level directory to be processed Next initialize a RecursiveIteratorIterator object (this is an Iterator designed solely for the purpose of iterating overother recursive Iterators) and pass it the newly minted RecursiveDirectoryIteratorYou can now process the results with a foreach() loopYou can read more about the RecursiveDirectoryIterator and the RecursiveIteratorIterator at httpwwwphpnet~hellyphpextsplFor more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo n ldquo617Deleting Directoriesrdquo and ldquo620 Searching for Files in a Directoryrdquo You can also readabout recursively processing arrays in the listing in ldquo43 Processing Nested Arraysrdquo

612 Printing Directory TreesProblemYou want to print a hierarchical listing of a directory and its contents

SolutionWrite a recursive function to traverse the directory and print its contentsltpregtltphp function to recursively process a directory and all its subdirectories and print a hierarchical listfunction printTree($dir $depth=0) check if argument is a valid directoryif (is_dir($dir)) die(Argument is not a directory) open directory handle$dh = opendir($dir) or die (Cannot open directory) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory (branch) print it and go deeperecho str_repeat( $depth) [$file]nprintTree($dir$file ($depth+1)) else if this is a file (leaf) print itecho str_repeat( $depth) $filen

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 14: Files nts

recursively process and print directory treeprintTree(test)gtltpregt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children printing the name of every element found A depthcounter is incremented every time the function enters a subdirectory the str_repeat() function uses this depth counter to pad the listing with spaces and thussimulate a hierarchical treeFor more examples of recursively processing a directory tree see the listings inldquo615 Copying Directoriesrdquo and ldquo617 Deleting Directoriesrdquo

613 Copying FilesProblemYou want to copy a file from one location to another

SolutionUse PHPrsquos copy() functionltphp set file name$source = dummytxt$destination = dummytxtbackup copy file if it exists else exitif (file_exists($source)) copy ($source $destination) or die (Cannot copy file $source)echo File successfully copied else die (Cannot find file $source)gt

CommentsIn PHP creating a copy of a file is as simple as calling the copy() function andpassing it the source and destination file names and paths The function returns trueif the file is successfully copiedIf what you really want is to create a copy of a directory visit the listing in ldquo615Copying DirectoriesrdquoNOTEIf the destination file already exists it will be overwritten with no warning by copy() If this isnot what you want implement an additional check for the target file with file_exists()and exit with a warning if the file already exists

614 Copying Remote FilesProblemYou want to create a local copy of a file located on a remote server

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 15: Files nts

SolutionUse PHPrsquos file_get_contents() and file_put_contents() functions toread a remote file and write the retrieved data to a local fileltphp increase script execution time limitini_set(max_execution_time 600) set URL of file to be downloaded$remoteFile = httpwwwsomedomainremotefiletgz set name of local copy$localFile = localfiletgz read remote file$data = file_get_contents($remoteFile) or crarrdie(Cannot read from remote file) write data to local filefile_put_contents($localFile $data) or crarrdie(Cannot write to local file) display success messageecho File [$remoteFile] successfully copied to [$localFile]gt

210 P H P P r o g r a m m i n g S o l u t i o n s

CommentsMost of PHPrsquos file functions support reading from remote files In this listingthis capability is exploited to its fullest to create a local copy of a remote fileThe file_get_contents() function reads the contents of a remote file into astring and the file_put_contents() function then writes this data to a local filethereby creating an exact copy Both functions are binary-safe so this technique canbe safely used to copy both binary and non-binary files

615 Copying DirectoriesProblemYou want to copy a directory and all its contents including subdirectories

SolutionWrite a recursive function to travel through a directory copying files as it goesltphp function to recursively copy a directory and its subdirectoriesfunction copyRecursive($source $destination) check if source existsif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($source$file)) if this is a subdirectory recursively copy itcopyRecursive($source$file $destination$file) else if this is a file

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 16: Files nts

copy itcopy ($source$file $destination$file)crarror die (Cannot copy file $file) close directoryclosedir($dh) copy directory recursivelycopyRecursive(wwwtemplate wwwsite12)echo Directories successfully copiedgt

CommentsThis listing is actually a combination of techniques discussed in the listings in ldquo611Recursively Processing Directoriesrdquo and ldquo613 Copying Filesrdquo Here the customcopyRecursive() function iterates over the source directory and dependingon whether it finds a file or directory copies it to the target directory or invokesitself recursively The recursion ends when no further subdirectories are left to betraversed Note that if the target directory does not exist at any stage it is createdwith the mkdir() function

616 Deleting FilesProblemYou want to delete a file

SolutionUse PHPrsquos unlink() functionltphp set file name$file = shakespeareasc

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 itif (file_exists($file)) unlink ($file) or die(Cannot delete file $file)echo File successfully deleted else die (Cannot find file $file)gt

CommentsTo delete a file with PHP simply call the unlink() function with the file name andpath The function returns true if the file was successfully deletedNOTETypically PHP will not be able to delete files owned by other users the PHP process can only deletefiles owned by the user itrsquos running as This is a common cause of errors so keep an eye out for it

617 Deleting DirectoriesProblemYou want to delete a directory and its contents including subdirectories

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 17: Files nts

SolutionWrite a recursive function to travel through a directory and its children deleting filesas it goesltphp function to recursively delete a directory and its subdirectoriesfunction deleteRecursive($dir) check if argument is a valid directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively delete itdeleteRecursive($dir$file) else if this is a file delete itunlink ($dir$file) or die (Cannot delete file crarr$file) close directoryclosedir($dh) remove top-level directoryrmdir($dir) delete directory recursivelydeleteRecursive(junkrobert)echo Directories successfully deletedgt

CommentsIn PHP the function to remove a directory a rmdir() Unfortunately this functiononly works if the directory in question is empty Therefore to delete a directory itis first necessary to iterate over it and delete all the files within it If the directorycontains subdirectories those need to be deleted too you do this by entering themand erasing their contentsThe most efficient way to accomplish this task is with a recursive function suchas the one in the previous listing which is a combination of the techniques outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo and the listing in ldquo616Deleting Filesrdquo Here the deleteRecursive() function accepts a directory pathand name and goes to work deleting the files in it If it encounters a directory itinvokes itself recursively to enter that directory and clean it up Once all the contentsof a directory are erased you use the rmdir() function to remove it completely214 P H P P r o g r a m m i n g S o l u t i o n s

618 Renaming Files and Directories

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 18: Files nts

ProblemYou want to move or rename a file or directory

SolutionUse PHPrsquos rename() functionltphp set old and new filedirectory names$oldFile = homejohn$newFile = homejane check if filedirectory exists if it does moverename itif (file_exists($oldFile)) rename ($oldFile $newFile)crarror die(Cannot moverename file $oldFile)echo Filesdirectories successfully renamed else die (Cannot find file $oldFile)gt

CommentsA corollary to PHPrsquos copy() function you can use the rename() function to bothrename and move files Like copy() rename()accepts two arguments a sourcefile and a destination file and attempts to rename the former to the latter It returnstrue on success

619 Sorting FilesProblemYou want to sort a file listingC 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

SolutionSave the file list to an array and then use the array_multisort() function to sortit by one or more attributesltphp define directory$dir = testa check if it is a directoryif (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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) add an entry to the file list for this file$fileList[] = array(name =gt $file size =gt crarrfilesize($dir$file) date =gt filemtime($dir$file)) close directoryclosedir($dh) separate all the elements with the same key into individual arraysforeach ($fileList as $key=gt$value) $name[$key] = $value[name]$size[$key] = $value[size]$date[$key] = $value[date]

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 19: Files nts

now sort by one or more keys sort by namearray_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 sizearray_multisort($date $size $fileList)print_r($fileList)gt

CommentsHere PHPrsquos directory functions are used to obtain a list of the files in a directoryand place them in a two-dimensional array This array is then processed with PHPrsquosarray_multisort() function which is especially good at sorting symmetricalmultidimensional arraysThe array_multisort() function accepts a series of input arrays and usesthem as sort criteria Sorting begins with the first array values in that array thatevaluate as equal are sorted by the next array and so on This makes it possible tosort the file list first by size and then date or by name and then size or any otherpermutation thereof Once the file list has been sorted it can be processed furtheror displayed in tabular form

620 Searching for Files in a DirectoryProblemYou want to find all the files matching a particular name pattern starting from a toplevelsearch directory

SolutionWrite a recursive function to search the directory and its children for matching filenamesltphp function to recursively search directories for matching filenamesfunction searchRecursive($dir $pattern) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare array to hold matchesglobal $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 directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itsearchRecursive($dir$file $pattern) else if this is a file check for a match add to $matchList if foundif (preg_match($pattern $file)) $matchList[] = $dir$file

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 20: Files nts

return the final list to the callerreturn $matchList search for file names containing ini$fileList = searchRecursive(cwindows ini)print_r($fileList)gt

CommentsThis listing is actually a variant of the technique outlined in the listing in ldquo611Recursively Processing Directoriesrdquo Here a recursive function travels through thenamed directory and its children using the preg_match() function to check eachfile name against the name pattern Matching file names and their paths are stored inan array which is returned to the caller once all subdirectories have been processedAn alternative approach here involves using the PEAR File_Find class availablefrom httppearphpnetpackageFile_Find This class exposes asearch() method which accepts a search pattern and a directory path and performsa recursive search in the named directory for files matching the search pattern Thereturn value of the method is an array containing a list of paths to the matching files218 P H P P r o g r a m m i n g S o l u t i o n sHerersquos an illustration of this class in actionltphp include File_Find classinclude FileFindphp search recursively for file names containing tgz returns array of paths to matching files$fileList = File_Findsearch(tgz tmp)print_r($fileList)gt

For more examples of recursively processing a directory tree see the listings inldquo611 Recursively Processing Directoriesrdquo ldquo615 Copying Directoriesrdquo and ldquo617Deleting Directoriesrdquo

621 Searching for Files in PHPrsquosDefault Search PathProblemYou want to check if a particular file exists in PHPrsquos default search path and obtainthe full path to it

SolutionScan PHPrsquos include_path for the named file and if found obtain the full path toit with PHPrsquos realpath() functionltphp function to check for a file in the PHP include pathfunction 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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 21: Files nts

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 foundforeach ($searchList as $dir) if (file_exists($dir$file)) return realpath($dir$file) return false look for the file DBphp$result = searchIncludePath(DBphp)echo $result File was found in $result File was not foundgt

CommentsA special PHP variable defined through the phpini configuration file the include_path variable typically contains a list of directories that PHP will automatically lookin for files include-d or require-d by your script It is similar to the Windows$PATH variable or the Perl INC variableIn this listing the directory list stored in this variable is read into the PHP scriptwith the ini_get() function and a foreach() loop is then used to iterate over thelist and check if the file exists If the file is found the realpath() function is usedto obtain the full file system path to the file

622 Searching and ReplacingPatterns Within FilesProblemYou want to perform a searchreplace operation within one or more files

SolutionUse PEARrsquos File_SearchReplace classltphp include File_SearchReplace classinclude FileSearchReplacephp

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(PHPcrarrPHP Hypertext Pre-Processor array(chapter_01txt crarrchapter_02txt)) perform the search write the changes to the file(s)$fsr-gtdoReplace() get the number of matchesecho $fsr-gtgetNumOccurences() match(es) foundgt

CommentsTo perform search-and-replace operations with one or more files yoursquoll needthe PEAR File_SearchReplace class available from httppearphpnetpackageFile_SearchReplace Using this class itrsquos easy to replace patternsinside one or more filesThe object constructor requires three arguments the search term the replacement

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 22: Files nts

text and an array of files to search in The searchreplace operation is performedwith the doReplace() method which scans each of the named files for the searchterm and replaces matches with the replacement text The total number of matchescan always be obtained with the getNumOccurences() methodTIPYou can use regular expressions for the search term and specify an array of directories (instead offiles) as an optional fourth argument to the object constructor Itrsquos also possible to control whetherthe search function should comply with Perl or PHP regular expression matching norms Moreinformation on how to accomplish these tasks can be obtained from the class documentation andsource code

623 Altering File ExtensionsProblemYou want to change all or some of the file extensions in a directoryC 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

SolutionUse PHPrsquos glob() and rename() functionsltphp define directory path$dir = test define old and new extensions$newExt = asc$oldExt = txt search for files matching patternforeach (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 extensionrename ($file $name$newExt) crarror die (Cannot rename file $file)echo $count file(s) renamedgt

CommentsPHPrsquos glob() function builds a list of files matching a particular pattern andreturns an array with this information Itrsquos then a simple matter to iterate over thisarray extract the filename component with substr() and rename the file with thenew extension

624 Finding Differences Between FilesProblemYou want to perform a UNIX diff on two files222 P H P P r o g r a m m i n g S o l u t i o n s

SolutionUse PEARrsquos Text_Diff class

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 23: Files nts

ltpregtltphp include Text_Diff classinclude TextDiffphpinclude TextDiffRendererphpinclude TextDiffRendererunifiedphp define files to compare$file1 = rhyme1txt$file2 = rhyme2txt compare files$diff = ampnew Text_Diff(file($file1) file($file2)) initialize renderer and display diff$renderer = ampnew Text_Diff_Renderer_unified()echo $renderer-gtrender($diff)gtltpregt

CommentsThe UNIX diff program is a wonderful way of quickly identifying differencesbetween two strings PEARrsquos Text_Diff class available from httppearphpnetpackageText_Diff brings this capability to PHP making it possible toeasily compare two strings and returning the difference in standard diff formatThe input arguments to the Text_Diff object constructor must be two arrays ofstring values Typically these arrays contain the lines of the files to be comparedand are obtained with the file() function The Text_Diff_Renderer class takes careof displaying the comparison in UNIX diff format via the render() method ofthe objectAs an illustration herersquos some sample output from this listing -12 +13 They all ran after the farmers 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

625 ldquoTailingrdquo FilesProblemYou want to ldquotailrdquo a file or watch it update in real time on a Web page

SolutionDisplay the output of the UNIX tail program on an auto-refreshing Web pagelthtmlgtltheadgtltmeta http-equiv=refresh content=5url=lt=$_SERVER[PHP_SELF]gtgtltheadgtltbodygtltpregtltphp set name of log file$file = tmprootproclog set number of lines to tail$limit = 10 run the UNIX tail command and display the outputsystem(usrbintail -$limit $file)gtltpregtltbodygtlthtmlgt

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 24: Files nts

CommentsUNIX administrators commonly use the tail program to watch log files update inreal time A common requirement in Web applications especially those that interactwith system processes is to have this real-time update capability available within theapplication itselfThe simplestmdashthough not necessarily most elegantmdashway to do this is to usePHPrsquos system() function to fork an external tail process and display its output ona Web page A ltmeta http-equiv=refresh gt tag at the top of thepage causes it to refresh itself every few seconds thereby producing an almostreal-time update224 P H P P r o g r a m m i n g S o l u t i o n sNOTEForking an external process from PHP is necessarily a resource-intensive process To avoidexcessive usage of system resources tune the page refresh interval in this listing to correctlybalance the requirements of real-time monitoring and system resource usage

626 Listing Available Drivesor Mounted File SystemsProblemYou want a list of available drives (Windows) or mounted file systems (UNIX)

SolutionUse the is_dir() function to check which drive letters are valid (Windows)ltphp loop from a to z check which are active drives place active drives in an arrayforeach(range(az) as $drive) if (is_dir($drive)) $driveList[] = $drive print array of active drive lettersprint_r($driveList)gt

Read the etcmtab file for a list of active mount points (UNIX)ltphp read mount information from mtab file$lines = file(etcmtab) or die (Cannot read file) iterate over lines in file get device and mount point add to arrayforeach ($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 mountsprint_r($mountList)gt

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 25: Files nts

CommentsFor Web applications that interact with the file systemmdashfor example an interactivefile browser or disk quota managermdasha common requirement involves obtaininga list of valid system drives (Windows) or mount points (UNIX) The previouslistings illustrate simple solutions to the problemWindows drive letters always consist of a single alphabetic character So to findvalid drives use the is_dir() function to test the range of alphabetic charactersfrom A to Z and retain those for which the function returns trueA list of active UNIX mounts is usually stored in the system file etcmtab(although your UNIX system may use another file or even the proc virtual filesystem) So to find valid drives simply read this file and parse the informationwithin it

627 Calculating Disk UsageProblemYou want to calculate the total disk space used by a disk partition or directory

SolutionUse PHPrsquos disk_free_space() and disk_total_space() functions tocalculate the total disk usage for a partitionltphp 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 - $freeecho $used MB usedgt

Write a recursive function to calculate the total disk space consumed by a particulardirectoryltphp function to recursively process a directory and all its subdirectoriesfunction calcDirUsage($dir) check if argument is a valid directoryif (is_dir($dir)) die(Argument $dir is not a directory) declare variable to hold running totalglobal $byteCount open directory handle$dh = opendir($dir) or die (Cannot open directory $dir) iterate over files in directorywhile (($file = readdir($dh)) == false) filter out and if ($file = ampamp $file = ) if (is_dir($dir$file)) if this is a subdirectory recursively process itcalcDirUsage($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 callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 26: Files nts

add its size to the running total$byteCount += filesize($dir$file) return the final list to the callerreturn $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(cwindows)$used = round($bytes1048576)echo $used MB usedgt

CommentsPHPrsquos disk_total_space() and disk_free_space() functions return themaximum and available disk space for a particular drive or partition respectivelyin bytes Subtracting the latter from the former returns the number of bytes currentlyin use on the partitionObtaining the disk space used by a specific directory and its subdirectories issomewhat more complex The task here involves adding the sizes of all the filesin that directory and its subdirectories to arrive at a total count of bytes used Thesimplest way to accomplish this is with a recursive function such as the one outlinedin the previous listing where file sizes are calculated and added to a running totalDirectories are deal with recursively in a manner reminiscent of the technique outlinedin the listing in ldquo611 Recursively Processing Directoriesrdquo The final sum will be thetotal bytes consumed by the directory and all its contents (including subdirectories)TIPTo convert byte values to megabyte or gigabyte values for display divide by 1048576 or1073741824 respectively

628 Creating Temporary FilesProblemYou want to create a temporary file with a unique name perhaps as a flag orsemaphore for other processes

SolutionUse PHPrsquos tempnam() functionltphp create temporary file with prefix tmp$filename = tempnam(tmp tmp)echo Temporary file [$filename] successfully createdgt

228 P H P P r o g r a m m i n g S o l u t i o n s

CommentsPHPrsquos tempnam() function accepts two arguments a directory name and a fileprefix and attempts to create a file using the prefix and a randomly generatedidentifier in the specified directory If the file is successfully created the functionreturns the complete path and name to itmdashthis can then be used by other file

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 27: Files nts

functions to write data to itThis listing offers an easy way to quickly create a file for temporary use perhapsas a signal to other processes Note however that the file created by tempnam()must be manually deleted with unlink() once itrsquos no longer requiredTIPPHPrsquos tmpfile() function creates a unique temporary file that exists only for the duration ofthe script Read more about this function at httpwwwphpnettmpfile

629 Finding the System Temporary DirectoryProblemYou want to retrieve the path to the systemrsquos temporary directory

SolutionUse PHPrsquos tempnam() function to create a temporary file and then obtain the pathto itltphp create a temporary file and get its name result Temporary directory is tmp$tmpfile = tempnam(thisdirectorydoesnotexist tmp)unlink ($tmpfile)echo Temporary directory is dirname($tmpfile)gt

CommentsThe tempnam() function provides an easy way to create a temporary file on thesystem Such a file is typically used as a semaphore or flag for other processesmdashforexample it can be used for file locking processes or status indicators The returnvalue of the tempnam() function is the full path to the newly minted file Given thisC 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 229file is always created in the systemrsquos temporary directory running the dirname()function on the complete file path produces the required informationNOTEIn this listing the first parameter to tempnam() is a nonexistent directory path Why Welltempnam() normally requires you to specify the directory in which the temporary file is tobe created Passing a nonexistent directory path forces the function to default to the systemrsquostemporary directory thereby making it possible to identify the locationAn alternative approach consists of using the PEAR File_Util class availableat httppearphpnetpackageFile This class exposes a tmpDir()method which returns the path to the system temporary directory Take a lookltphp include File_Util classinclude FileUtilphp get name of system temporary directory result Temporary directory is tmp$tmpdir = File_UtiltmpDir()echo Temporary directory is $tmpdirgt

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 28: Files nts

630 Converting Between Relativeand Absolute File PathsProblemYou want to convert a relative path to an absolute path

SolutionUse PHPrsquos realpath() functionltphp result usrlocalapachehtdocs (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 usrlocalapache (example)echo realpath() result usrlocalecho realpath(usrlocalmysqldata)gt

CommentsTo convert a relative path to an absolute file path use PHPrsquos realpath() functionThis function performs ldquopath mathrdquo translating all the relative locations in a pathstring to return a complete absolute file pathNOTEOn a tangential note take a look at PEARrsquos File_Util class available from httppearphpnetpackageFile which comes with a method to calculate the differencebetween two file paths The following simple example illustratesltphp include File_Util classinclude FileUtilphp define two locations on the filesystem$begin = usrlocalapache$end = varspoolmail figure out how to get from one to the other result varspoolmailecho File_UtilrelativePath($end $begin )gt

631 Parsing File PathsProblemYou want to extract the path file name or extension path from a file pathC 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

SolutionUse PHPrsquos pathinfo() function to automatically split the file path into itsconstituent partsltphp define path and filename$path = etcsendmailcf decompose path into constituents$data = pathinfo($path)print_r($data)gt

CommentsThe pathinfo() function is one of PHPrsquos more useful path manipulation functions

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 29: Files nts

Pass it a file path and pathinfo() will split it into its individual components Theresulting associative array contains separate keys for directory name file name andfile extension You can then easily access and use these keys for further processingmdashfor example the variable $data[dirname] will return the value etcTIPWhen parsing file paths also consider using the basename() dirname() andrealpath() functions You can read about these functions in the PHP manual at httpwwwphpnetfilesystem

From PHP Solutions Dynamic Web Design Made Easy Second Editionpdf

Chapter 7Using PHP to Manage Files

PHP has a huge range of functions designed to work with the server1048577s file system but finding the right onefor the job isn1048577t always easy This chapter cuts through the tangle to show you some practical uses ofthese functions such as reading and writing text files to store small amounts of information without adatabase Loops play an important role in inspecting the contents of the file system so you1048577ll also exploresome of the Standard PHP Library (SPL) iterators that are designed to make loops more efficientAs well as opening local files PHP can read public files such as news feeds on other servers Newsfeeds are normally formatted as XML (Extensible Markup Language) In the past extracting informationfrom an XML file was tortuous process but that1048577s no longer the case thanks to the very aptly namedSimpleXML that was introduced in PHP 5 In this chapter I1048577ll show you how to create a drop-down menuthat lists all images in a folder create a function to select files of a particular type from a folder pull in alive 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 you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 30: Files nts

it in the browser As an added bonus you1048577ll learn how to change the time zone of a date retrieved fromanother websiteThis chapter covers the following subjectsReading and writing filesListing the contents of a folderInspecting files with the SplFileInfo classControlling loops with SPL iteratorsUsing SimpleXML to extract information from an XML fileConsuming an RSS feedCreating a download link

Checking that PHP has permission to open a fileAs 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 accessHosting companies can impose further restrictions on file access through phpini To find out whatrestrictions have been imposed run phpinfo() on your website and check the settings in the Coresection Table 7-1 lists the settings you need to check Unless you run your own server you normallyhave no control over these settingsTable 7-1 PHP configuration settings that affect file accessDirective Default value Descriptionallow_url_fopen On Allows PHP scripts to open public files on the Internetallow_url_include Off Controls the ability to include remote filesopen_basedir no value Restricts accessible files to the specified directory treeEven if no value is set restrictions may be set directly in theserver configurationsafe_mode Off Mainly restricts the ability to use certain functions (fordetails see wwwphpnetmanualenfeaturessafemodefunctionsphp) This feature has been deprecatedsince PHP 53 and will be removed at a future datesafe_mode_include_dir no value If safe_mode is enabled user and group ID checks areskipped when files are included from the specified directorytree

Accessing remote filesArguably the most important setting in Table 7-1 is allow_url_fopen If it1048577s disabled you cannot accessuseful external data sources such as news feeds and public XML documents Prior to PHP 52allow_url_fopen also allowed you to include remote files in your pages This represented a majorsecurity risk prompting many hosting companies to disabled allow_url_fopen The security risk waseliminated in PHP 52 by the introduction of a separate setting for including remote filesallow_url_include which is disabled by defaultAfter PHP 52 was released not all hosting companies realized that allow_url_fopen had changed andcontinued to disable it Hopefully by the time you read this the message will have sunk in thatallow_url_fopen allows you to read remote files but not to include them directly in your scripts If yourhosting company still disables allow_url_fopen ask it to turn it on Otherwise you won1048577t be able to usePHP Solution 7-5 If the hosting company refuses you should consider moving to one with a betterunderstanding of PHP

Configuration settings that affect local file accessIf the Local Value column for open_basedir or safe_mode_include_dir displays no value you canignore this section However if it does have a value the meaning depends on whether the value ends witha trailing slash like thishomeincludesIn this example you can open or include files only from the includes directory or any of itssubdirectoriesIf the value doesn1048577t have a trailing slash the value after the last slash acts as a prefix For examplehomeinc gives you access to homeinc homeincludes homeincredible and so onmdashassuming 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 testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 31: Files nts

happens when you try to access a file outside the limits imposed by open_basedir

Creating a file storage folder for local testingStoring data inside your site root is highly insecure particularly if you need to set global accesspermissions on the folder If you have access to a private folder outside the site root create your datastore as a subfolder and give it the necessary permissionsFor the purposes of this chapter I suggest that Windows users create a folder called private on their Cdrive Mac users should create a private folder inside their home folder and then set Read amp Writepermissions in the folder1048577s Info panel as described in the previous chapter

Reading and writing filesThe restrictions described in the previous section reduce the attraction of reading and writing files withPHP Using a database is more convenient and offers greater security However that assumes you haveaccess to a database and the necessary knowledge to administer it So for small-scale data storage andretrieval working directly with text files is worth considering

Reading files in a single operationThe 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 fileThis PHP solution demonstrates how to use file_get_contents() and readfile() and explains howthey differ1 Create a text file in your private folder type some text into it and save it asfiletest_01txt (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 calledget_contentsphp in the new folder Insert the following code inside a PHP block (get_contents_01php in the ch07 folder shows the code embedded in a web page but youcan use just the PHP code for testing purposes)echo file_get_contents(Cprivatefiletest_01txt)If you1048577re on a Mac amend the pathname like this using your own Mac usernameecho file_get_contents(Usersusernameprivatefiletest_01txt)If you1048577re testing on a remote server amend the pathname accordinglyFor brevity the remaining examples in this chapter show only the Windows pathname3 Save get_contentsphp and view it in a browser Depending on what you wrote infiletest_01txt you should see something like the following screenshotYou shouldn1048577t see any error messages on your local system unless you typed the codeincorrectly or you didn1048577t set the correct permissions on a Mac However on a remote systemyou may see error messages similar to thisThe error messages in the preceding screenshot were created on a local system todemonstrate what happens when open_basedir has been set either in phpini or on theserver They mean you1048577re trying to access a file outside your permitted file structure The firsterror message should indicate the allowed paths On a Windows server each path isseparated by a semicolon On Linux the separator is a colon4 Change the code in get_contentsphp like this (it1048577s in get_contents_02php)readfile(Cprivatefiletest_01txt)5 Save get_contentsphp and reload it in your browser The contents of the text file aredisplayed as beforeSo what1048577s the difference The original code uses echo to display the contents of the file Theamended code doesn1048577t use echo In other words file_get_contents() simply gets thecontents of a file but readfile() also displays it immediately The advantage offile_get_contents() is that you can assign the file contents to a variable and process it insome way before deciding what to do with it6 Change the code in get_contentsphp like this (or use get_contents_03php) and load thepage into a browser get the contents of the file$contents = file_get_contents(Cprivatefiletest_01txt) 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 displayecho implode( $first)This stores the contents of filetest_01txt in a variable which is passed to the explode()

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 32: Files nts

function This alarmingly named function ldquoblows apartrdquo a string and converts it into an arrayusing the first argument to determine where to break the string In this case a space is usedso the contents of the text file are split into an array of wordsThe array of words is then passed to the array_slice() function which takes a slice out ofan array starting from the position specified in the second argument The third argumentspecifies the length of the slice PHP counts arrays from 0 so this extracts the first fourwordsFinally implode() does the opposite of explode() joining the elements of an array andinserting the first argument between each one The result is displayed by echo producing thefollowing outcomeInstead of displaying the entire contents of the file the script now displays only the first fourwords The full string is still stored in $contents7 If you need to extract the first few words from a string on a regular basis you could create acustom function like thisfunction 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_04php)$contents = file_get_contents(Cprivatefiletest_01txt)echo getFirstWords($contents 7)8 Among the dangers with accessing external files are that the file might be missing or its namemisspelled Change the code like this (it1048577s in get_contents_05php)$contents = file_get_contents(Cprivatefiletest_01txt)if ($contents === false) echo Sorry there was a problem reading the file else echo $contentsIf the file_get_contents() function can1048577t open the file it returns false Often you cantest for false by using the logical Not operator like thisif ($contents) If the file is empty or contains only the number 0 $contents is implicitly false To make surethe returned value is explicitly false you need to use the identical operator (three equalsigns)9 Test the page in a browser and it should display the contents of the text file as before10 Replace the contents of filetest_01txt with the number 0 Save the text file and reloadget_contentsphp in the browser The number displays correctly11 Delete the number in the text file and reload get_contentsphp You should get a blankscreen but no error message The file loads but doesn1048577t contain anything12 Change the code in get_contentsphp so that it attempts to load a nonexistent file Whenyou load the page you should see an ugly error message like thisldquoFailed to open streamrdquo means it couldn1048577t open the fileUSING PHP TO MANAGE FILES18513 This is an ideal place to use the error control operator (see Chapter 4) Insert an markimmediately in front of the call to file_get_contents() like this (the code is inget_contents_07php)$contents = file_get_contents(Cprivatefiletest0txt)14 Test get_contentsphp in a browser You should now see only the following custom errormessageAlways add the error control operator only after testing the rest of a script When developing youneed to see error messages to understand why something isn1048577t working the way you expectText files can be used as a flat-file databasemdashwhere each record is stored on a separate line with a tabcomma or other delimiter between each field (see httpenwikipediaorgwikiFlat_file_database) With this sort of file it1048577s more convenient to store each line individually inan array to process with a loop The file() function builds the array automatically

PHP Solution 7-2 Reading a text file into an arrayTo demonstrate the file() function this PHP solution uses filetest_02txt which contains just twolines as follows

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 33: Files nts

david codeslavechris bigbossThis will be used as the basis for a simple login system to be developed further in Chapter 91 Create a PHP file called filephp inside the filesystem folder Insert the following code (oruse file_01php from the ch07 folder)ltphp read the file into an array called $users$users = file(Cprivatefiletest_02txt)gtltpregtltphp print_r($users) gtltpregtThis draws the contents of filetest_02txt into an array called $users and then passes itto print_r() to display the contents of the array The ltpregt tags simply make the outputeasier to read in a browser2 Save the page and load it in a browser You should see the following outputIt doesn1048577t look very exciting but now that each line is a separate array element you can loopthrough the array to process each line individually3 You need to use a counter to keep track of each line a for loop is the most convenient (seeldquoThe versatile for looprdquo in Chapter 3) To find out how many times the loop should run pass thearray to the count() function to get its length Amend the code in filephp like this (or usefile_02php)ltphp read the file into an array called $users$users = file(Cprivatefiletest03txt) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt $tmp[1])gtltpregtltphp print_r($users) gtltpregtThe count() function returns the length of an array so in this case the value ofcount($users) is 2 This means the first line of the loop is equivalent to thisfor ($i = 0 $i lt 2 $i++) The loop continues running while $i is less than 2 Since arrays are always counted from 0this means the loop runs twice before stoppingInside the loop the current array element ($users[$i]) is passed to the explode() functionIn this case the separator is defined as a comma followed by a space ( ) However youcan use any character or sequence of characters using t (see Table 3-4 in Chapter 3) asthe first argument to explode() turns a tab-separated string into an arrayUSING PHP TO MANAGE FILES187The first line in filetest 02txt looks like thisdavid codeslaveWhen 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 bigboss4 Save filephp and view it in a browser The result looks like this5 Take a close look at the gap between codeslave and the closing parenthesis of the firstsubarray The file() function doesn1048577t remove newline characters or carriage returns so youneed to do it yourself Pass the final item of $tmp to rtrim() like this$users[$i] = array(name =gt $tmp[0] password =gt rtrim($tmp[1]))The rtrim() function removes whitespace (spaces tabs newline characters and carriagereturns) 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 astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 34: Files nts

from the beginning of a string and trim() which removes whitespace from both ends of astringIf you1048577re 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 itscontents so wrap the main PHP block in a conditional statement like this (see file_03php)$textfile = Cprivatefiletest_02txtif (file_exists($textfile) ampamp is_readable($textfile)) read the file into an array called $users$users = file($textfile) loop through the array to process each linefor ($i = 0 $i lt 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 =gt $tmp[0] password =gt rtrim($tmp[1])) else echo Cant open $textfileTo avoid typing out the file pathname each time begin by storing it in a variableThis simple script extracts a useful array of names and associated passwords You could also use thiswith a series of sports statistics or any data that follows a regular pattern

Opening and closing files for readwrite operationsThe functions we have looked at so far do everything in a single pass However PHP also has a set offunctions that allow you to open a file read it andor write to it and then close the file The following are themost important functions used for this type of operationfopen() Opens a filefgets() Reads the contents of a file normally one line at a timefread() Reads a specified amount of a filefwrite() Writes to a filefeof() Determines whether the end of the file has been reachedrewind() Moves an internal pointer back to the top of the filefclose() Closes a fileThe first of these fopen() is the most difficult to understand mainly because you need to specify howthe file is to be used once it1048577s open fopen() has one read-only mode three write-only modes and fourreadwrite modes Sometimes you want to overwrite the existing content At other times you may want toappend new material At yet other times you may want PHP to create a file if it doesn1048577t already existThe other thing you need to understand is where each mode places the internal pointer when it opens thefile It1048577s like the cursor in a word processor PHP starts reading or writing from wherever the pointerhappens to be when you call fread() or fwrite()Table 7-2 brings order to the confusionUSING PHP TO MANAGE FILES189Table 7-2 Readwrite modes used with fopen()Type Mode DescriptionRead-only r Internal pointer initially placed at beginning of fileWrite-only w Existing data deleted before writing Creates a file if it doesn1048577t already exista Append mode New data added at end of file Creates a file if it doesn1048577t alreadyexistx Creates a file only if it doesn1048577t already exist so no danger of deleting existingdatar+ Readwrite operations can take place in either order and begin wherever theinternal pointer is at the time Pointer initially placed at beginning of file File mustalready exist for operation to succeedw+ Existing data deleted Data can be read back after writing Creates a file if itdoesn1048577t already exista+ Opens a file ready to add new data at end of file Also permits data to be readback after internal pointer has been moved Creates a file if it doesn1048577t alreadyexistReadwritex+ Creates a new file but fails if a file of the same name already exists Data can be

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 35: Files nts

read back after writingChoose the wrong mode and you could end up deleting valuable data You also need to be careful aboutthe position of the internal pointer If the pointer is at the end of the file and you try to read the contentsyou end up with nothing On the other hand if the pointer is at the beginning of the file and you startwriting you overwrite the equivalent amount of any existing data ldquoMoving the internal pointerrdquo later in thischapter explains this in more detail with a practical exampleYou work with fopen() by passing it the following two argumentsThe path to the file you want to openOne of the modes listed in Table 7-2The fopen() function returns a reference to the open file which can then be used with any of the otherreadwrite functions So this is how you would open a text file for reading$file = fopen(Cprivatefiletest_02txt r)Thereafter you pass $file as the argument to other functions such as fgets() and fclose() Thingsshould become clearer with a few practical demonstrations Rather than building the files yourself you1048577llprobably find it easier to use the files in the ch07 folder I1048577ll run quickly through each modeCHAPTER 7190Mac 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_readphp contains the following code store the pathname of the file$filename = Cprivatefiletest_02txt 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 filefclose($file) display the contentsecho nl2br($contents)If you load this into a browser you should see the following outputUnlike file_get_contents() the function fread() needs to know how much of the file to read So youneed to supply a second argument indicating the number of bytes This can be useful if you want sayonly the first 100 characters of a text file However if you want the whole file you need to pass the file1048577spathname to filesize() to get the correct figureThe nl2br() function in the final line converts new line characters to HTML ltbr gt tagsThe other way to read the contents of a file with fopen() is to use the fgets() function which retrievesone line at a time This means that you need to use a while loop in combination with feof() to read rightthrough 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_readloopphp) create variable to store the contents$contents = loop through each line until end of filewhile (feof($file)) retrieve next line and add to $contents$contents = fgets($file)USING PHP TO MANAGE FILES191The while loop uses fgets() to retrieve the contents of the file one line at a timemdashfeof($file) is thesame as saying until the end of $filemdashand stores them in $contentsBoth methods are more long-winded than file() or file_get_contents() However you need to useeither 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 it1048577s useful for working withfiles that need to be updated frequently You can test the w mode with fopen_writephp which has thefollowing PHP code above the DOCTYPE declarationltphp if the form has been submitted process the input text

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 36: Files nts

if (isset($_POST[contents])) open the file in write-only mode$file = fopen(Cprivatefiletest_03txt w) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)gtThere1048577s no need to use a loop this time you1048577re just writing the value of $contents to the opened file Thefunction fwrite() takes two arguments the reference to the file and whatever you want to write to itIn other books or scripts on the Internet you may come across fputs() instead of fwrite() Thetwo functions are identical fputs() is a synonym for fwrite()If you load fopen_writephp into a browser type something into the text area and click Write to filePHP creates filetest_03txt and inserts whatever you typed into the text area Since this is just ademonstration I1048577ve omitted any checks to make sure that the file was successfully written Openfiletest_03txt to verify that your text has been inserted Now type something different into the textarea and submit the form again The original content is deleted from filetest_03txt and replaced withthe 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 theend preserving any existing content The main code in fopen_appendphp is the same asfopen_writephp apart from those elements highlighted here in bold open the file in append mode$file = fopen(Cprivatefiletest_03txt a) write the contents after inserting new linefwrite($file PHP_EOL $_POST[contents]) close the filefclose($file)CHAPTER 7192Notice that I have concatenated PHP_EOL to the beginning of $_POST[contents] This is a PHPconstant that represents a new line on any operating system On Windows new lines require a carriagereturn and newline character but Macs traditionally use only a carriage return while Linux uses only anewline character PHP_EOL gets round this nightmare by automatically choosing the correct charactersdepending on the server1048577s operating systemIf you load fopen_appendphp into a browser and insert some text it should now be added to the end ofthe existing text as shown in the following screenshotThis is a very easy way of creating a flat-file database We1048577ll 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 theopposite of what you want To make sure you1048577re not overwriting an existing file you can use fopen() withx mode The main code in fopen_exclusivephp looks like this (changes are highlighted in bold) create a file ready for writing only if it doesnt already exist$file = fopen(Cprivatefiletest_04txt x) write the contentsfwrite($file $_POST[contents]) close the filefclose($file)If you load fopen_exclusivephp into a browser type some text and click Write to file the contentshould be written to filetest_04txt in your target folderIf you try it again you should get a series of error messages telling you that the file already exists

Combined readwrite operations with fopen()By adding a plus sign (+) after any of the previous modes the file is opened for both reading and writingYou can perform as many read or write operations as you likemdashand in any ordermdashuntil the file is closedThe difference between the combined modes is as followsr+ The file must already exist a new one will not be automatically created The internal pointeris placed at the beginning ready for reading existing contentw+ Existing content is deleted so there is nothing to read when the file is first openeda+ 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 readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 37: Files nts

pointer needs to be moved back before anything can be readx+ Always creates a new file so there1048577s nothing to read when the file is first openedReading is done with fread() or fgets() and writing with fwrite() exactly the same as before What1048577simportant is to understand the position of the internal pointerUSING PHP TO MANAGE FILES193Moving the internal pointerReading and writing operations always start wherever the internal pointer happens to be so you normallywant it to be at the beginning of the file for reading and at the end of the file for writingTo move the pointer to the beginning of a file pass the file reference to rewind() like thisrewind($file)Moving the pointer to the end of a file is more complex You need to use fseek() which moves the pointerto a location specified by an offset and a PHP constant The constant that represents the end of the file isSEEK_END so an offset of 0 bytes places the pointer at the end You also need to pass fseek() areference to the open file so all three arguments together look like thisfseek($file 0 SEEK_END)SEEK_END is a constant so it doesn1048577t need quotes and it must be in uppercase You can also usefseek() to move the internal pointer to a specific position or relative to its current position For detailssee httpdocsphpnetmanualenfunctionfseekphpThe file fopen_pointerphp uses the fopen() r+ mode to demonstrate combining several read andwrite operations and the effect of moving the pointer The main code looks like this$filename = Cprivatefiletest_04txt open a file for reading and writing$file = fopen($filename r+) the pointer is at the beginning so existing content is overwrittenfwrite($file $_POST[contents] ) read the contents from the current position$readRest = while (feof($file)) $readRest = fgets($file) reset internal pointer to the beginningrewind($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 againfwrite($file $_POST[contents]) read immediately without moving the pointer$readAgain = while (feof($file)) $readAgain = fgets($file)CHAPTER 7194 close the filefclose($file)The version of this file in the ch07 folder contains code that displays the values of $readRest $readAlland $readAgain to show what happens at each stage of the readwrite operations The existing content infiletest_04txt was This works only the first time When I typed New content infopen_pointerphp and clicked Write to file I got the results shown hereTable 7-3 describes the sequence of eventsTable 7-3 Sequence of readwrite operations in fopen_pointerphpCommand Position of pointer Result$file = fopen($filenamer+) Beginning of file File opened for processingfwrite($file $_POST[contents]) End of write operation Form contents overwritesbeginning of existing contentwhile (feof($file)) $readRest = fgets($file)End of file Remainder of existing contentread

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 38: Files nts

rewind($file) Beginning of file Pointer moved back to beginningof file$readAll = fread($file 1048577filesize($filename))See text Content read from beginning of filefwrite($file $_POST[contents]) At end of previousoperationForm contents addedat current position of pointerwhile (feof($file)) $readAgain = fgets($file)End of file Nothing read because pointer wasalready at end of filefclose($file) Not applicable File closed and all changes savedUSING PHP TO MANAGE FILES195When I opened filetest_04txt this is what it containedIf you study the code in fopen_pointerphp you1048577ll notice that the second read operation uses fread()It works perfectly with this example but contains a nasty surprise Change the code infopen_pointerphp to add the following line after the external file has been opened (it1048577s commented outin 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 scriptfread() ignores the text added at the end of the file This is because the external file is still open sofilesize() reads its original size Consequently you should always use a while loop with feof() andfgets() if your read operation takes place after any new content has been written to a fileThe changes to a file with read and write operations are saved only when you call fclose() or whenthe script comes to an end Although PHP saves the file if you forget to use fclose() you shouldalways close the file explicitly Don1048577t get into bad habits one day they may cause your code to breakand lose valuable dataWhen you create or open a file in a text editor you can use your mouse to highlight and delete existingcontent or position the insertion point exactly where you want You don1048577t have that luxury with a PHPscript so you need to give it precise instructions On the other hand you don1048577t need to be there when thescript runs Once you have designed it it runs automatically every time

Exploring the file systemPHP1048577s file system functions can also open directories (folders) and inspect their contents You put one ofthese functions to practical use in PHP Solution 6-5 by using scandir() to create an array of existingfilenames in the images folder and looping through the array to create a unique name for an uploaded fileFrom the web developer1048577s point of view other practical uses of the file system functions are building dropdownmenus displaying the contents of a folder and creating a script that prompts a user to download afile such as an image or PDF document

Inspecting a folder with scandir()Let1048577s take a closer look at the scandir() function which you used in PHP Solution 6-5 It returns an arrayconsisting 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 7196You can examine the result by using print_r() to display the contents of the array as shown in thefollowing screenshot (the code is in scandirphp in the ch07 folder)As you can see the array returned by scandir() doesn1048577t contain only files The first two items are knownas dot files which represent the current and parent folders The third item is a folder called _notes andthe penultimate item is a folder called thumbsThe array contains only the names of each item If you want more information about the contents of afolder it1048577s better to use the DirectoryIterator class

Inspecting the contents of a folder with DirectoryIteratorThe DirectoryIterator class is part of the Standard PHP Library (SPL) which has been part of PHP

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 39: Files nts

since PHP 50 The SPL offers a mind-boggling assortment of specialized iterators that allow you to createsophisticated loops with very little code As the name suggests the DirectoryIterator class lets youloop through the contents of a directory or folderBecause it1048577s a class you instantiate a DirectoryIterator object with the new keyword and pass thepath of the folder you want to inspect to the constructor like this$files = new DirectoryIterator(images)Unlike scandir() this doesn1048577t return an array of filenamesmdashalthough you can loop through $files in thesame way as an array Instead it returns an SplFileInfo object that gives you access to a lot moreinformation about the folder1048577s contents than just the filenames Because it1048577s an object you can1048577t useprint_r() to display its contentsHowever if all you want to do is to display the filenames you can use a foreach loop like this (the code isin iterator_01php in the ch07 folder)$files = new DirectoryIterator(images)foreach ($files as $file) echo $file ltbrgtUSING PHP TO MANAGE FILES197This produces the following resultAlthough using echo in the foreach loop displays the filenames the value stored in $file each time theloop runs is not a string In fact it1048577s another SplFileInfo object Table 7-4 lists the main SplFileInfomethods that can be used to extract useful information about files and foldersTable 7-4 File information accessible through SplFileInfo methodsMethod ReturnsgetFilename() The name of the filegetPath() The current object1048577s relative path minus the filename or minus the folder name ifthe current object is a foldergetPathName() The current object1048577s relative path including the filename or folder namedepending on the current typegetRealPath() The current object1048577s full path including filename if appropriategetSize() The size of the file or folder in bytesisDir() True if the current object is a folder (directory)isFile() True if the current object is a fileisReadable() True if the current object is readableisWritable() True if the current object is writableCHAPTER 7198The RecursiveDirectoryIterator class burrows down into subfolders To use it you wrap it in thecuriously named RecursiveIteratorIterator like this (the code is in iterator_03php)$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(images))foreach ($files as $file) echo $file-gtgetRealPath() ltbrgtAs the following screenshot shows the RecursiveDirectoryIterator inspects the contents of allsubfolders revealing the contents of the thumbs and _notes folders in a single operationHowever what if you want to find only certain types of files Cue another iterator

Restricting file types with the RegexIteratorThe 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 youneed to look for any of the following filename extensions jpg png or gif The regex used to searchfor these filename extensions looks like this(jpg|png|gif)$iIn spite of its similarity to Vogon poetry this regex matches image filename extensions in a caseinsensitivemanner The code in iterator_04php 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-gtgetRealPath() ltbrgtUSING PHP TO MANAGE FILES

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 40: Files nts

199The original $files object is passed to the RegexIterator constructor with the regex as the secondargument and the filtered set is stored in $images The result is thisOnly image files are now listed The folders and other miscellaneous files have been removed Before PHP5 the same result would have involved many more lines of complex loopingTo learn more about the mysteries of regular expressions (regexes) see my two-part tutorial atwwwadobecomdevnetdreamweaverarticlesregular_expressions_pt1html As you progressthrough this book you1048577ll see I make frequent use of regexes They1048577re a useful tool to add to your skillsetI expect that by this stage you might be wondering if this can be put to any practical use OK let1048577s build adrop-down menu of images in a folder

PHP Solution 7-3 Building a drop-down menu of filesWhen you work with a database you often need a list of images or other files in a particular folder Forinstance you may want to associate a photo with a product detail page Although you can type the nameof the image into a text field you need to make sure that the image is there and that you spell its namecorrectly Get PHP to do the hard work by building a drop-down menu automatically It1048577s always up-todateand there1048577s no danger of misspelling the name1 Create a PHP page called imagelistphp in the filesystem folder Alternatively useimagelist_01php in the ch07 folderCHAPTER 72002 Create a form inside imagelistphp and insert a ltselectgt element with just one ltoptiongtlike this (the code is already in imagelist_01php)ltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltselectgtltformgtThis ltoptiongt is the only static element in the drop-down menu3 Amend the form like thisltform id=form1 name=form1 method=post action=gtltselect name=pix id=pixgtltoption value=gtSelect an imageltoptiongtltphp$files = new DirectoryIterator(images)$images = new RegexIterator($files (jpg|png|gif)$i)foreach ($images as $image) gtltoption value=ltphp echo $image gtgtltphp echo $image gtltoptiongtltphp gtltselectgtltformgt4 Make sure that the path to the images folder is correct for your site1048577s folder structure5 Save imagelistphp and load it into a browser You should see a drop-down menu listing allthe images in your images folder as shown in Figure 7-1USING PHP TO MANAGE FILES201Figure 7-1 PHP makes light work of creating a drop-down menu of images in a specific folderWhen incorporated into an online form the filename of the selected image appears in the$_POST array identified by the name attribute of the ltselectgt elementmdashin this case$_POST[pix] That1048577s all there is to itYou can compare your code with imagelist_02php in the ch07 folder

PHP Solution 7-4 Creating a generic file selectorThe previous PHP solution relies on an understanding of regular expressions Adapting it to work withother filename extensions isn1048577t difficult but you need to be careful that you don1048577t accidentally delete avital character Unless regexes or Vogon poetry are your specialty it1048577s probably easier to wrap the code ina function that can be used to inspect a specific folder and create an array of filenames of specific typesFor example you might want to create an array of PDF document filenames or one that contains bothPDFs and Word documents Here1048577s how you do it1 Create a new file called buildlistphp in the filesystem folder The file will contain onlyPHP code so delete any HTML inserted by your editing program

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 41: Files nts

CHAPTER 72022 Add the following code to the filefunction 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 extensionor an array of filename extensions To keep the code simple the filenameextensions should not include a leading periodThe function begins by checking whether $dir is a folder and is readable If it isn1048577t thefunction returns false and no more code is executedIf $dir is OK the else block is executed It also begins with a conditional statement thatchecks whether $extensions is an array If it is it1048577s passed to implode() which joins thearray elements with a vertical pipe (|) between each one A vertical pipe is used in regexes toindicate alternative values Let1048577s say the following array is passed to the function as thesecond argumentarray(jpg png gif)The conditional statement converts it to jpg|png|gif So this looks for jpg or png or gifHowever if the argument is a string it remains untouched3 You can now build the regex search pattern and pass both arguments to theDirectoryIterator and RegexIterator like thisfunction 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 FILES203The regex pattern is built using a string in double quotes and wrapping $extensions in curlybraces to make sure it1048577s interpreted correctly by the PHP engine Take care when copying thecode It1048577s not exactly easy to read4 The final section of the code extracts the filenames to build an array which is sorted and thenreturned The finished function definition looks like thisfunction 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-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 42: Files nts

foreach ($files as $file) $filenames[] = $file-gtgetFilename() sort the array and return itnatcasesort($filenames)return $filenamesThis initializes an array and uses a foreach loop to assign the filenames to it with thegetFilename() method Finally the array is passed to natcasesort() which sorts it in anatural case-insensitive order What ldquonaturalrdquo means is that strings that contain numbers aresorted in the same way as a person would For example a computer normally sorts img12jpgbefore img2jpg because the 1 in 12 is lower than 2 Using natcasesort() results inimg2jpg preceding img12jpg5 To use the function use as arguments the path to the folder and the filename extensions ofthe files you want to find For example you could get all Word and PDF documents from afolder like this$docs = buildFileList(folder_name array(doc docx pdf))The code for the buildFileList() function is in buildlistphp in the ch07 folder

Accessing remote filesReading writing and inspecting files on your local computer or on your own website is useful Butallow_url_fopen also gives you access to publicly available documents anywhere on the Internet Youcan1048577t directly include files from other serversmdashnot unless allow_url_include is onmdashbut you can readCHAPTER 7204the content save it to a variable and manipulate it with PHP functions before incorporating it in your ownpages or saving the information to a database You can also write to documents on a remote server aslong as the owner sets the appropriate permissionsA word of caution is in order here When extracting material from remote sources for inclusion in your ownpages there1048577s a potential security risk For example a remote page might contain malicious scriptsembedded in ltscriptgt tags or hyperlinks Unless the remote page supplies data in a known format from atrusted sourcemdashsuch as product details from the Amazoncom database weather information from agovernment meteorological office or a newsfeed from a newspaper or broadcastermdashsanitize the contentby passing it to htmlentities() (see PHP Solution 5-3) As well as converting double quotes to ampquothtmlentities() converts lt to amplt and gt to ampgt This displays tags in plain text rather than treatingthem as HTMLIf you want to permit some HTML tags use the strip_tags() function instead If you pass a string tostrip_tags() it returns the string with all HTML tags and comments stripped out It also removes PHPtags A second optional argument is a list of tags that you want preserved For example the followingstrips out all tags except paragraphs and first- and second-level headings$stripped = strip_tags($original ltpgtlth1gtlth2gt)For an in-depth discussion of security issues see Pro PHP Security by Chris Snyder and MichaelSouthwell (Apress 2005 ISBN 978-1-59059-508-4)

Consuming news and other RSS feedsSome of the most useful remote sources of information that you might want to incorporate in your sitescome from RSS feeds RSS stands for Really Simple Syndication and it1048577s a dialect of XML XML is similarto HTML in that it uses tags to mark up content Instead of defining paragraphs headings and imagesXML tags are used to organize data in a predictable hierarchy XML is written in plain text so it1048577sfrequently used to share information between computers that might be running on different operatingsystemsFigure 7-2 shows the typical structure of an RSS 20 feed The whole document is wrapped in a pair ofltrssgt tags This is the root element similar to the lthtmlgt tags of a web page The rest of the document iswrapped in a pair of ltchannelgt tags which always contains the following three elements that describe theRSS feed lttitlegt ltdescriptiongt and ltlinkgtUSING PHP TO MANAGE FILES205rsschannellinktitle link pubDate description

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 43: Files nts

hannetitle description item itemubDat criptioFigure 7-2 The main contents of an RSS feed are in the item elementsIn addition to the three required elements the ltchannelgt can contain many other elements but theinteresting material is to be found in the ltitemgt elements In the case of a news feed this is where theindividual news items can be found If you1048577re looking at the RSS feed from a blog the ltitemgt elementsnormally contain summaries of the blog postsEach ltitemgt element can contain several elements but those shown in Figure 7-2 are the mostcommonmdashand usually the most interestinglttitlegt The title of the itemltlinkgt The URL of the itemltpubDategt Date of publicationltdescriptiongt Summary of the itemThis predictable format makes it easy to extract the information from an RSS feed using SimpleXMLYou can find the full RSS Specification at wwwrssboardorgrss-specification Unlike mosttechnical specifications it1048577s written in plain language and easy to read

Using SimpleXMLAs long as you know the structure of an XML document SimpleXML does what it says on the tin it makesextracting information from XML simple The first step is to pass the URL of the XML document tosimplexml_load_file() You can also load a local XML file by passing the path as an argument Forexample this gets the world news feed from the BBC$feed = simplexml_load_file(httpfeedsbbcicouknewsworldrssxml)This creates an instance of the SimpleXMLElement class All the elements in the feed can now beaccessed as properties of the $feed object using the names of the elements With an RSS feed theltitemgt elements can be accessed as $feed-gtchannel-gtitemCHAPTER 7206To display the lttitlegt of each ltitemgt create a foreach loop like thisforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgtIf you compare this with Figure 7-2 you can see that you access elements by chaining the element nameswith the -gt operator until you reach the target Since there are multiple ltitemgt elements you need to usea loop to tunnel further down Alternatively use array notation like this$feed-gtchannel-gtitem[2]-gttitleThis gets the lttitlegt of the third ltitemgt element Unless you want only a specific value it1048577s simpler touse a loopWith that background out of the way let1048577s use SimpleXML to display the contents of a news feed

PHP Solution 7-5 Consuming an RSS news feedThis PHP solution shows how to extract the information from a live news feed using SimpleXML anddisplay it in a web page It also shows how to format the ltpubDategt element to a more user-friendly formatand how to limit the number of items displayed using the LimitIterator class1 Create a new page called newsfeedphp in the filesystem folder This page will contain amixture of PHP and HTML2 The news feed chosen for this PHP solution is the BBC World News A condition of using mostnews feeds is that you acknowledge the source So add The Latest from BBC Newsformatted as an lth1gt heading at the top of the pageSee httpnewsbbccouk1hihelprss4498287stm for the full terms and conditions ofusing a BBC news feed on your own site3 Create a PHP block below the heading and add the following code to load the feed$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url)4 Use a foreach loop to access the ltitemgt elements and display the lttitlegt of each oneforeach ($feed-gtchannel-gtitem as $item) echo $item-gttitle ltbrgt5 Save newsfeedphp and load the page in a browser You should see a long list of news itemssimilar to Figure 7-3USING PHP TO MANAGE FILES

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 44: Files nts

207Figure 7-3 The news feed contains a large number of items6 The normal feed often contains 50 or more items That1048577s fine for a news site but you probablywant a shorter selection in your own site Use another SPL iterator to select a specific range ofitems Amend the code like this$url = httpfeedsbbcicouknewsworldrssxml$feed = simplexml_load_file($url SimpleXMLIterator)$filtered = new LimitIterator($feed-gtchannel-gtitem 0 4)foreach ($filtered as $item) echo $item-gttitle ltbrgtTo use SimpleXML with an SPL iterator you need to supply the name of theSimpleXMLIterator class as the second argument to simplexml_load_file() You canthen pass the SimpleXML element you want to affect to an iterator constructorIn this case $feed-gtchannel-gtitem is passed to the LimitIterator constructor TheLimitIterator 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 thefirst item and limits the number of items to fourThe foreach loop now loops over the $filtered result If you test the page again you1048577ll seejust four titles as shown in Figure 7-4 Don1048577t be surprised if the selection of headlines isdifferent from before The BBC News website is updated every minuteCHAPTER 7208Figure 7-4 The LimitIterator restricts the number of items displayed7 Now that you have limited the number of items amend the foreach loop to wrap the lttitlegtelements in a link to the original article and display the ltpubDategt and ltdescriptiongt itemsThe loop looks like thisforeach ($filtered as $item) gtlth2gtlta href=ltphp echo $item-gtlink gtgtltphp echo $item-gttitle 1048577gtltagtlth2gtltp class=datetimegtltphp echo $item-gtpubDate gtltpgtltpgtltphp echo $item-gtdescription gtltpgtltphp gt8 Save the page and test it again The links take you directly to the relevant news story on theBBC website The news feed is now functional but the ltpubDategt format follows the formatlaid down in the RSS specification as shown in the next screenshot9 To format the date and time in a more user-friendly way pass $item-gtpubDate to theDateTime class constructor and then use the DateTime format() method to display it10 Change the code in the foreach loop like thisltp class=datetimegtltphp $date= new DateTime($item-gtpubDate)echo $date-gtformat(M j Y gia) gtltpgtThis reformats the date like thisThe mysterious PHP formatting strings for dates are explained in Chapter 14USING PHP TO MANAGE FILES20911 That looks a lot better but the time is still expressed in GMT (London time) If most of yoursite1048577s visitors live on the East Coast of the United States you probably want to show the localtime That1048577s no problem with a DateTime object Use the setTimezone() method to change toNew 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 thecode like thisltp class=datetimegtltphp $date = new DateTime($item-gtpubDate)$date-gtsetTimezone(new DateTimeZone(AmericaNew_York))$offset = $date-gtgetOffset()$timezone = ($offset == -14400) EDT ESTecho $date-gtformat(M j Y gia) $timezone gtltpgtTo create a DateTimeZone object you pass it as an argument one of the time zones listed athttpdocsphpnetmanualentimezonesphp This is the only place that theDateTimeZone object is needed so it has been created directly as the argument to thesetTimezone() methodThere isn1048577t 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 CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 45: Files nts

the getOffset() method returns the number of seconds the time is offset from CoordinatedUniversal Time (UTC) The following line determines whether to display EDT or EST$timezone = ($offset == -14400) EDT ESTThis uses the value of $offset with the conditional operator In summer New York is 4 hoursbehind UTC (ndash14440 seconds) So if $offset is ndash14400 the condition equates to true andEDT is assigned to $timezone Otherwise EST is usedFinally 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 isloaded the time is adjusted to the East Coast of the United States like this12 All the page needs now is smartening up with CSS Figure 7-5 shows the finished news feedstyled with newsfeedcss in the styles folderYou can learn more about SPL iterators and SimpleXML in my PHP Object-Oriented Solutions (friendsof ED 2008 ISBN 978-1-4302-1011-5)CHAPTER 7210Figure 7-5 The live news feed requires only a dozen lines of PHP codeAlthough I have used the BBC News feed for this PHP solution it should work with any RSS 20 feed Forexample you can try it locally with httprsscnncomrsseditionrss Using a CNN news feed in apublic website requires permission from CNN Always check with the copyright holder for terms andconditions before incorporating a feed into a website

Creating a download linkA question that crops up regularly in online forums is ldquoHow do I create a link to an image (or PDF file) thatprompts the user to download itrdquo The quick solution is to convert the file into a compressed format suchas ZIP This frequently results in a smaller download but the downside is that inexperienced users maynot know how to unzip the file or they may be using an older operating system that doesn1048577t include anextraction facility With PHP file system functions it1048577s easy to create a link that automatically prompts theuser to download a file in its original format

PHP Solution 7-6 Prompting a user to download an imageThe script in this PHP solution sends the necessary HTTP headers opens the file and outputs itscontents as a binary stream1 Create a PHP file called downloadphp in the filesystem folder The full listing is given in thenext step You can also find it in downloadphp in the ch07 folderUSING PHP TO MANAGE FILES2112 Remove any default code created by your script editor and insert the following codeltphp define error page$error = httplocalhostphpsolserrorphp define the path to the download folder$filepath = Cxampphtdocsphpsolsimages$getfile = NULL block any attempt to explore the filesystemif (isset($_GET[file]) ampamp basename($_GET[file]) == $_GET[file]) $getfile = $_GET[file] else header(Location $error)exitif ($getfile) $path = $filepath $getfile check that it exists and is readableif (file_exists($path) ampamp is_readable($path)) get the files size and send the appropriate headers$size = filesize($path)header(Content-Type applicationoctet-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 cant be opened

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 46: Files nts

$file = fopen($path r)if ($file) stream the file and exit the script when completefpassthru($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 firstdefines $error a variable that contains the URL of your error page The second line thatneeds to be changed defines the path to the folder where the download file is storedThe script works by taking the name of the file to be downloaded from a query string appendedto the URL and saving it as $getfile Because query strings can be easily tampered withCHAPTER 7212$getfile is initially set to NULL This is an important security measure If you fail to do thisyou could give a malicious user access to any file on your serverThe opening conditional statement uses basename() to make sure that an attacker cannotrequest a file such as one that stores passwords from another part of your file structure Asexplained in Chapter 4 basename() extracts the filename component of a path so ifbasename($_GET[file]) is different from $_GET[file] you know there1048577s an attempt toprobe your server and you can stop the script from going any further by using the header()function to redirect the user to the error pageAfter checking that the requested file exists and is readable the script gets the file1048577s sizesends 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 can1048577t be opened ordoesn1048577t exist the user is redirected to the error page3 Test the script by creating another page and add a couple of links to downloadphp Add aquery string at the end of each link with file= followed by the name a file to be downloadedYou1048577ll find a page called getdownloadsphp in the ch07 folder which contains the followingtwo linksltpgtlta href=downloadphpfile=maikojpggtDownload image 1ltagtltpgtltpgtlta href=downloadphpfile=basinjpggtDownload image 2ltagtltpgt4 Click one of the links and the browser should present you with a dialog box prompting you todownload the file or choose a program to open it as shown in Figure 7-6Figure 7-6 The browser prompts the user to download the image rather than opening it directlyUSING PHP TO MANAGE FILES2135 Select Save File and click OK and the file should be saved rather than displayed ClickCancel to abandon the download Whichever button you click the original page remains in thebrowser window The only time downloadphp should load into the browser is if the file cannotbe opened That1048577s why it1048577s important to send the user to an error page if there1048577s a problemI1048577ve demonstrated downloadphp with image files but it can be used for any type of file because theheaders send the file as a binary streamThis script relies on header() to send the appropriate HTTP headers to the browser It is vital toensure that there are no new lines or whitespace ahead of the opening PHP tag If you have removedall whitespace and still get an error message saying ldquoheaders already sentrdquo your editor may haveinserted invisible control characters at the beginning of the file Some editing programs insert the byteorder mark (BOM) which is known to cause problems with the ability to use the header() functionCheck your program preferences to make sure the option to insert the BOM is deselected

Chapter reviewThe file system functions aren1048577t particularly difficult to use but there are many subtleties that can turn aseemingly simple task into a complicated one It1048577s important to check that you have the right permissionsEven when handling files in your own website PHP needs permission to access any folder where you wantto read files or write to themThe SPL DirectoryIterator and RecursiveDirectoryIterator classes make it easy to examine thecontents of folders Used in combination with the SplFileInfo methods and the RegexIterator youcan 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 hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__

Page 47: Files nts

When dealing with remote data sources you need to check that allow_url_fopen hasn1048577t been disabledOne of the most common uses of remote data sources is extracting information from RSS news feeds orXML documents a task that takes only a few lines of code thanks to SimpleXMLIn the next two chapters we1048577ll put some of the PHP solutions from this chapter to further practical usewhen working with images and building a simple user authentication systemCHAPTER 7214__