CLIL-3-PHP-3 Files - part 1 Introduction Once you master the art of working with files, a wider world of PHP web development opens up to you. Files aren't as flexible as databases by any means, but they do offer the chance to easily and permanently store information across scripts, which makes them popular amongst programmers. Files, as you can imagine, can store all sorts of information. However, most file formats (e.g. picture formats such as PNG and JPEG) are binary, and very difficult and/or impossible to write using normal text techniques - in these situations you should use the library designed to cope with each format. One reminder: if you are using an operating system that uses backslash \ as the path separator (e.g. Windows), you need to escape the backslash with another backslash, making \\. Alternatively, just use /, because Windows understands that just fine too. Topics covered in this chapter are: Reading and writing files Temporary files How to make a counter Handling file uploads File permissions 1 CLIL-3-PHP-3 Note: CPUs work in billions of operations per second - a 3GHz CPU is capable of performing three billion operations every second. RAM access time is measured in nanoseconds (billionths of a second) - you can usually access some data in RAM in about 40 nanoseconds. Hard drive access time, however, is measured in milliseconds (thousandths of a second) - most hard drive have about a 7ms access time. What this means is that hard drives are much, much slower than RAM, so working with files from your hard drive is the slowest part of your computer, excluding your CD ROM. Databases are able to store their data in RAM for much faster access time, whereas storing hard drive data in RAM is tricky. Files are good for storing small bits of information, but it is not recommended that you use them too much for anything other than your PHP scripts themselves - counters are fine in files, as are other little things, but anything larger would almost certainly benefit from using a database. Having said that, please try to avoid the newbie mistake of putting everything into your database - if you find yourself trying to figure out what field type is right to store picture data, please have a rethink! 2 CLIL-3-PHP-3 readfile() int readfile (string filename [, bool use_include_path [, resource context]]) If you want to output a file to the screen without doing any form of text processing on it whatsoever, readfile() is the easiest function to use. When passed a filename as its only parameter, readfile() will attempt to open it, read it all into memory, then output it without further question. If successful, readfile() will return an integer equal to the number of bytes read from the file. If unsuccessful, readfile() will return false, and there are quite a few reasons why it may file. For example, the file might not exist, or it might exist with the wrong permissions. Here is an example script: <?php readfile("/home/paul/test.txt"); readfile("c:\\boot.ini"); ?> The first example will work on most Unix-like systems, and will attempt to open the file test.txt in /home/paul/ directory - you will almost certainly want to change this to point to somewhere that exists and you have access to. The second example is for Windows systems, and should work in any Windows NT-descended OS, including Windows XP, Vista, 7 and 8. The advantages to using readfile() are clear: there is no fuss, and there is little way for it to go wrong. However, the disadvantage is equally clear: you have got absolutely no control over the text that comes out. You should use readfile() when you just want to print a file out without any further action on your behalf - it is a powerful little one-liner. 3 CLIL-3-PHP-3 Note that from here on in I will be using the variable $filename to signify a filename you have chosen. This is to avoid having to keep printing separate examples for Windows and Unix. 4 CLIL-3-PHP-3 file_get_contents() file() string file_get_contents (string filename [, bool use_include_path [, resource context]]) array file (string filename [, bool use_include_path [, resource context]]) The next evolutionary step up from readfile() is just called file_get_contents(), and also takes one parameter for the filename to open. This time, however, it does not output any data - instead, it will return the contents of the file as string, replete with new line characters \n where appropriate Here is file_get_contents() in use: <?php $filestring = file_get_contents($filename); print $filestring; ?> $filename, as mentioned already, is a variable used to represent a file you have chosen already, whether that be on Unix or Windows, which means that file_get_contents() opens that file and places its contents into $filestring. Effectively that piece of code is the same as our call to readfile(), but only because we're not doing anything with $filestring once we have it. Consider this script: <?php $filestring = file_get_contents($filename); $filearray = explode("\n", $filestring); while (list($var, $val) = each($filearray)) { ++$var; $val = trim($val); print "Line $var: $val<br />"; } ?> 5 CLIL-3-PHP-3 This time we use explode() to turn $filestring into an array, which is then iterated through, outputting one line at a time with line numbers. Remember that array indices start at 0, so we need ++$var to make sure that it starts at line 1 rather than line 0. Also, note that we call trim() on $val - this is because each element in the array still has its new line character \n at the end, and trim() will take that off. File_get_contents() is an excellent general-purpose file-handling function that you will likely find yourself using extensively. As an alternative, if you find yourself always wanting your files inside arrays, you can use the file() function - it works in the same manner as file_get_contents(), with the exception that it returns an array, which each line in the file returned as an element. 6 CLIL-3-PHP-3 fopen() fread() resource fopen ( string filename, string mode [, bool use_include_path [, resource context]]) string fread ( resource handle, int length) fopen() is, for many, a fiendishly complex function to use. The reason for this is because it is another one of those functions lifted straight from C, and so is not as user-friendly as most PHP functions. On the flip-side, as per usual, is the fact that fopen() is an incredibly versatile function that some people come to love for its ability to manipulate files just as you want it to. fopen() has two key parameters: the file to open, and how you would like it opened. Parameter one is easy - it is just $filename, as with the other examples. Parameter two is what makes fopen() so special: you specify letters in a string that define whether you want to read from ("r"), write to ("w"), or append to ("a") the file specified in parameter one. There is also a fourth option, "b", which opens the file in binary mode. This is not necessary on Unix-based systems, but it is on Windows, so it is best to use it everywhere - it is not detrimental on Unix-based systems at all. Take a look at the following usages: <?php fopen($filename, "r"); fopen($filename, "w"); ?> fopen() returns a file handle resource, which is basically the contents of the file. You cannot output it directly, e.g. "print fopen(....)", but fopen() -related functions all accept file handles as the file to work with, so you should store the return value 7 CLIL-3-PHP-3 of fopen() in a variable for later use. Therefore, here's a full usage of fopen() : <?php $handle = fopen($filename, "a"); ?> If the file cannot be opened, fopen() returns false. If the file is successfully opened, a file handle is returned and you can proceed. Once the file handle is ready, we can call other functions on the opened file, depending on how the file was opened (the second parameter to fopen() ). To read from a file, the function fread() is used, and to write to a file fwrite() is used. For now we're interested in reading, so you should use "rb" for the second parameter to fopen(). fread() takes two parameters: a file handle to read from (this is the return value from fopen(), remember) and the number of bytes to read. The second parameter might not make sense at first, after all you generally want to read in all of a file, right? Note: Do not worry about specifying a number in parameter two that is larger than the file - PHP will stop reading when it hits the end of the file or the number of bytes in parameter two, whichever comes first. Well, while the chances are that you will indeed want to read in all of a file, doing so requires you have enough memory to be able to read in the entire file at once if you are working with files of several megabytes or indeed hundreds of megabytes, PHP's resource consumption would balloon. In circumstances such as these you have but two choices: buy more RAM, or parse your data sequentially! 8 CLIL-3-PHP-3 Luckily, most people will not have to make that choice - most people work with text files under a megabyte in size, and PHP can load a megabyte file all at once in a tiny fraction of a second. To instruct PHP to use fread() to read in the entire contents of a file, you simply need to specify the exact file size in bytes of that file as parameter two to fread(). Sound difficult? PHP comes to the rescue again with the filesize() function, which takes the name of a file to check, and returns its filesize in bytes - precisely what we're looking for. When reading in a file, PHP uses a file pointer to determine which byte it is currently up to - kind of like the array cursor. Each time you read in a byte, PHP advances the array cursor by one place - reading in the entire file at once advances the array cursor to the end of the file. So, to use fread() to read in an entire file, we can use the following line: <?php $contents = fread($handle, filesize($filename)); ?> Notice that fread() 's return value is the text it read in, and in above situation that is the entire file. To finish off using fread() it is simply necessary to close the file as soon as you are done with it. Note: Although PHP automatically closes all files you left open in your script, it is not smart to rely on it to do so - it is a poor use of resources, and it might affect other processes trying to read the file. Always, always, always close your files the minute you are finished with them. To close a file you have opened with fopen(), use fclose() - it takes the file handle we got from fopen(), and returns true if it was able to close the file successfully. 9 CLIL-3-PHP-3 We have now got enough to use fopen() to fully open and read in a file, then close it. Here is the script: <?php $handle = fopen($filename, "rb"); $contents = fread($handle, filesize($filename)); fclose($handle); print $contents; ?> Firstly, remember that you will need to set $filename to be the location of a file on your system that you have access to. Secondly, notice that fopen() is called with "fb" as the second parameter - read-only, binary-safe. Always use "b" to ensure compatibility with Windows servers. Thirdly, notice that filesize() is being used to fread() in all of $filename's contents. Finally, notice that fclose() is called before $contents is printed - it is closed as soon as $handle is no longer needed. Get into - and stay in - this habit. 10 CLIL-3-PHP-3 Creating and changing files Like reading files, creating and changing files can also be done in more than one way. Luckily for you, there are just two options this time: file_put_contents() and fwrite(). Both of these functions complement functions we just looked at, which are file_get_contents() and fread() respectively, and they work in mostly the same way. file_put_contents() int file_put_contents ( string filename, string data [, int flags [, resource context]]) This function writes to a file with the equivalent of fopen(), fwrite() (the opposite of fread() ), and fclose() - all in one function, just like file_get_contents. It takes two parameters, the filename to write to and the content to write respectively, with a third optional parameter specifying extra flags which we will get to in a moment. If file_put_contents() is successful it returns the number of bytes written to the file, otherwise it will return false. Using the file_put_contents() function is easy - here's an example: <?php $myarray[] = "This is line one"; $myarray[] = "This is line two"; $myarray[] = "This is line three"; $mystring = implode("\n", $myarray); $numbytes = file_put_contents($filename, $mystring); print "$numbytes bytes written\n"; ?> Remember you will need to set $filename first - other than that the script is straightforward, and should output "52 bytes written", which is the sum total of 11 CLIL-3-PHP-3 the three lines of text plus the two new line characters used to implode() the array. Remember that the new line character is in fact just one character inside files, whereas PHP represents it using two, \ and n. You can pass in a third parameter to file_put_contents(), which, if set to FILE_APPEND, will act to append the text in your second parameter to the existing text in the file. If you do not use FILE_APPEND the existing text will be wiped and replaced, which is not always the desired behaviour. 12 CLIL-3-PHP-3 fwrite() int fwrite ( resource handle, string string [, int length]) The opposite to fread() is fwrite() and also works with the file handle returned by fopen(). Fwrite() takes a string to write as a second parameter, and an optional third parameter where you can specify how many bytes to write. If you do not specify the third parameter, all of the second parameter is written out to the file, which is often what you want. Note: as with fread(), PHP will stop writing when it reaches the end of the string or when it has reached the number of bytes specified in this length parameter, whichever comes first - you don't need to worry about specifying more bytes than you have in the string. Here is an example using the variable $mystring from the previous example to save space: <?php $handle = fopen($filename, "wb"); $numbytes = fwrite($handle, $mystring); fclose($handle); print "$numbytes bytes written\n"; ?> If I had added 10 as the third parameter to the fwrite() call, only the first ten bytes of $mystring would have been written out. Note again that fclose() is called immediately after it was finished with, which is always best practice. Fwrite() uses a file pointer in the same way as fread() - as you write bytes out, PHP advances the file pointer appropriately, meaning that, unless you specifically 13 CLIL-3-PHP-3 move the file pointer yourself, you always write to the end of a file. 14 CLIL-3-PHP-3 Moving, copying, and deleting files bool rename ( string old_name, string new_name [, resource context]) bool copy ( string source, string dest) bool unlink ( string filename, resource context) PHP has simple functions to handle all moving, copying, and deleting files, and quite rightly - they are all very popular things to do, so there is no point making them difficult. If you are using Unix you will know that there is no command for "rename", because renaming a file is essentially the same as moving it, so you use the move (mv) command - it is the same in PHP. Files are moved using rename(), copied using copy(), and deleted using unlink(). Unlink() might seem like an odd choice of word at first, but Unix systems consider filenames to be "hard links" to the actual files themselves, so to unlink a file is to delete it. Note: all three functions will operate without further input from you. If you choose to pass an existing file to the second parameter of rename(), it will rename the file in parameter one to the file in parameter two, overwriting the original file. The same applies to copy() - you will overwrite all files without question as long as you have the correct permissions. 15 CLIL-3-PHP-3 Moving files with rename() Use for both renaming and moving files, rename() takes two parameters: the original filename and the new filename you wish to use. Rename() can rename/move files across directories and drives, and will return true on success or false otherwise. Here is an example: <?php $filename2 = $filename . '.old'; rename($filename, $filename2); ?> If you had $filename set to c:\\windows\\myfile.txt, the above script would move that file to c:\\windows\\myfile.txt.old. Note: rename() should be used to move ordinary files, and not files uploaded through a form. The reason for this is because there is a special function, called move_uploaded_file(), which checks to make sure the file has indeed been uploaded before moving it - this stops people trying to hack your server into making private files visible. You can perform this check yourself if you like by calling the is_uploaded_file() function. 16 CLIL-3-PHP-3 Copying files with copy() Like rename(), copy() also takes two parameters - the filename you wish to copy from, and the filename you wish to copy to. The difference between rename() and copy() is that calling rename() results in the file being in only one place, the destination, whereas copy() leaves the file in the source location as well as placing a new copy of the file into the destination. <?php $filename2 = $filename . '.old'; copy($filename, $filename2); ?> The result of that script is that there will be a file $filename and also a $filename.old, e.g. c:\\windows\\myfile.txt and c:\\windows\\myfile.txt.old. Note: this function will not copy empty (zero-length) files - to do that, you need to use the function touch(). 17 CLIL-3-PHP-3 Deleting files with unlink() To delete files, simply pass a filename string as the only parameter to unlink(). Note that unlink deals only with files - to delete directories you need rmdir(). <?php if (unlink($filename)) { print "Deleted $filename!\n"; } else { print "Delete of $filename failed!\n"; } ?> Note: if you have a file opened with fopen(), you need to fclose() it before you call unlink() 18 CLIL-3-PHP-3 Temporary files resource tmpfile ( void ) Very often you will find you want to work with a file as if it were a "scratchpad" - a holding area where you can write out temporary data for later use. To make this as easy as possible, PHP has a function called tmpfile() which takes no parameters, but will create a temporary file on the system, fopen() it for you, and send back the file handle as its return value. That file is then yours to read from and write to all you wish, and is deleted as soon as you fclose() the file handle or the script ends. Here is an example of tmpfile() in use: <?php $handle = tmpfile(); $numbytes = fwrite($handle, $mystring); fclose($handle); print "$numbytes bytes written\n"; ?> As you can see, tmpfile() is a drop-in replacement for fopen()ing a known file, so it is easy to make use of. If you want to know where these temp files are being saved, use the sys_get_temp_dir() function - it's new in PHP 5.2.1, but returns a string containing the directory used for creating temporary files. 19