This section presents a complete example demonstrating file uploads and database interaction. We begin with a form where a user can submit information to create a new account, along with an image file. The data from this form will be submitted to a page that will then create this account and submit the image file to the database. We then create a page to show the new user account details along with the image, which will be fetched from the database (see Figure 25-2). Figure 25-2. From submitting to viewing a new user account with an avatar image.Setting UpTo make this sample work, we create a new table in one of our databases (MySQL syntax shown) that contains a minimal set of information about the user and details about the image and its data: CREATE TABLE AvatarSample ( user_id INTEGER AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(200) NOT NULL UNIQUE, full_name VARCHAR(200), password VARCHAR(100), email VARCHAR(200) NOT NULL, avatar_image BLOB, file_type VARCHAR(100), file_size INTEGER ); We now describe and show the various scripts we need to make this sample work. The New Account FormThis form is very similar to ones we have seen before. It lets the user enter some information for an account and upload a file: <html> <head> <title>Create New User</title> </head> <body> <form name="newUser" enctype="multipart/form-data" action="submit_account.php" method="POST"> User Name: <input type="text" name="userName"/><br/> Full Name: <input type="text" name="fullName"/><br/> Password: <input type="password" name="password1"/><br/> Re-Enter Password: <input type="password" name="password2"/><br/> <br/> Email Address: <input type="text" name="email"/><br/> <br/> User Image: <input type='file' name='avatarfile'/> <br/> <input type="submit" value="Submit"/> </form> </body> </html> Note that we are using the new form enctype we first saw in the section "The Client Form." If you find that file uploads do not seem to be working for some reason, remember that it is very frequently a result of having forgotten to add this to the form element. Creating the New AccountWhen users click Submit, they are taken to submit_account.php. This script processes the information the user gives us and then submits the data to the database. Of particular interest is the extra information it saves about the uploaded image file. For any image we want to store in our database, we will in fact store the following information:
We will need all of this information when we try to retrieve the image data later on. The addslashes function takes whatever data you pass to it and returns that same data with any instances of single quotes ('), double quotes ("), backslashes (\), or NULL bytes escaped with a backslash. Even though we are inserting binary data into our database, we will pass it to our database server using a text SQL query, so we need to be sure that it does not misinterpret characters in the byte stream. Note that we do not need to unescape the data when fetching it. Our code to insert the user account into the database performs four basic tasks:
Notice that in this sample, to save on space and keep the sample code reasonably focused, we do not perform full validation on all input data. We do make sure that the input file is of an image type we recognize and is less than 50,000 bytes, but for the rest of the input, we just make sure that it is SQL safe. The addslashes function needs to know the size of the data it is being given. We determine this by using the filesize function, as follows: $f = @fopen($_FILES['avatarfile']['tmp_name'], 'r'); if ($f === NULL) throw new FileAccessException(); $fsize = @filesize($_FILES['avatarfile']['tmp_name']); $fileData = addslashes(fread($f, $fsize)); fclose($f); When this section of code is complete, $fileData contains the data that is safe to insert into the SQL query we send to the database server. Viewing the User DataAfter the new account data is submitted, we let the user view it in the view_user.php script. This file just loads the information for the user and displays it. The big surprise in this script is that we do not load the image data just yet. Instead, we present a URL to the image data in an XHTML img tag, as follows: <img border='0' src='/books/3/445/1/html/2/get_img.php?uid=XX'/> We show the implementation of get_img.php in the section "Fetching the Image from the Database." The view_user.php script is as follows: <?php require_once('dbinfo.inc'); require_once('errors.inc'); function emitUserInfo($in_dbrow) { echo <<<EOINFO <b>User Name:</b> {$in_dbrow['user_name']}<br/> <b>Full Name:</b> {$in_dbrow['full_name']}<br/> <b>Email Address:</b> {$in_dbrow['email']}<br/> <b>Avatar:</b> <img border='0' src='/books/3/445/1/html/2/get_img.php?uid={$in_dbrow['user_id']}'/> <br/> EOINFO; } // // make sure we got a user ID from somewhere. // if (!isset($_GET) and !isset($_GET['uid'])) die('need a user id'); $uid = intval($_GET['uid']); // // connect to the db. // $conn = @new mysqli(DB_HOST, DB_USR, DB_PW, DB_DB); if (mysqli_connect_errno() !== 0) throw new DatabaseErrorException(mysqli_connect_error()); // // get the record. // $query = <<<EOQUERY SELECT user_id, user_name, full_name, email FROM AvatarSample WHERE user_id = $uid EOQUERY; $results = @$conn->query($query); if ($results === FALSE) { $msg = $conn->error; $conn->close(); throw new DatabaseErrorException($msg); } while (($row = @$results->fetch_assoc()) !== NULL) { if ($row['user_id'] == $uid) { emitUserInfo($row); break; } } $results->close(); $conn->close(); ?> The output of the emitUserInfo function looks something like this: <b>User Name:</b> Jimmy<br/> <b>Full Name:</b> Jimmy the Enforcer<br/> <b>Email Address:</b> Jimmy@enforcersrus.com<br/> <b>Avatar:</b> <img border='0' src='/books/3/445/1/html/2/get_img.php?uid=3'/> <br/> The client browser then duly calls get_img.php to fetch the image for the user. Fetching the Image from the DatabaseThe final piece of the puzzle is the script to fetch the image from the database. The data is sent directly to the client as the output stream of the script, while we simultaneously use HTTP headers (via the header function) to indicate what we are sending the client and how large the data is. Our get_img.php script looks like this: <?php require_once('dbinfo.inc'); require_once('errors.inc'); // find the user ID we're going to use. if (!isset($_GET) and !isset($_GET['uid'])) die('need a user id'); $uid = intval($_GET['uid']); // connect to the db. $conn = @new mysqli(DB_HOST, DB_USR, DB_PW, DB_DB); if (mysqli_connect_errno() !== 0) throw new DatabaseErrorException(mysqli_connect_error()); // get the record. $query = <<<EOQUERY SELECT user_id, avatar_image, file_type, file_size FROM AvatarSample WHERE user_id = $uid EOQUERY; $results = @$conn->query($query); if ($results === FALSE) { $msg = $conn->error; $conn->close(); throw new DatabaseErrorException($msg); } while (($row = @$results->fetch_assoc()) !== NULL) { if ($row['user_id'] == $uid) break; } $results->close(); $conn->close(); if ($row !== NULL) { // send the headers and the image data. header("Content-type: {$row['file_type']}"); header("Content-length: {$row['file_size']}"); header("Content-Disposition: attachment; filename=$uid.jpg"); header("Content-Description: PHP Generated Data"); echo $row['avatar_image']; } ?> Back when we submitted the data for the user, we stored, in addition to the user information and image file data, the size and type of this file. We use that information now as part of the headers we send back to the client: header("Content-type: {$row['file_type']}"); header("Content-length: {$row['file_size']}"); header("Content-Disposition: attachment; filename=$uid.jpg"); header("Content-Description: PHP Generated Data"); With these lines of code, we are indicating that we are sending something like an "image/jpeg" as output, with the indicated file size, and giving it a reasonable filename in case the user wants to save it in his browser. The last line of code we execute is to send the binary data for the file to the client: echo $row['avatar_image']; With this, we have successfully stored binary data in our database and written scripts to retrieve it later on. Note that these scripts operate similarly to the way actual web servers implement file downloads. |