8.3. Writing the Image Handling Class

Your first step, of course, is to define the ImageHandler class. You want this class to be portable, so you should create a separate file for it called images.inc.php. You save this file in the inc folder (full path: /xampp/htdocs/simple_blog/inc/images.inc.php). After you create images.inc.php, insert the following code to define the class:

<?php

class ImageHandler
{

}

?>

8.3.1. Saving the Image

So far you've defined your class is defined; next, you need to determine what methods and properties you need to define.

You need a public method that saves the uploaded image in a folder and returns its path. You also need a public property that stores the folder you want your images saved in. This property is vital, so set it in your constructor.

To be as descriptive as possible, call your method for uploading an image processUploadedImage.. Call the property that holds the folder's location $save_dir.

In images.inc.php, define your properties and constructor first by adding the lines in bold:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }
}

?>

At this point, instantiating the ImageHandler class gives you an object that sets a folder where you can save your images. At last you're ready to save an image. Do this by creating your public processUploadedImage() method.

Next, define the method by adding the lines in bold to images.inc.php:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

/**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Process the image
    }
}

?>

NOTE

The block comment immediately before processUploadedImage() is a special comment known as a PHP DocBlock. This is a special comment that provides information about a class, property, or method. DocBlocks are indicated by opening a comment using /**, then providing a short description of the class, method, or property. This process also lists a method's list of parameters and its return value. All classes you write from now on will take advantage of DocBlocks to help you keep track of the code you're writing. An additional point of interest: Some IDEs and SDKs (including Eclipse) use DocBlocks to provide descriptions of methods as they're used, including a list of parameters (defined using @param [datatype] [var_name] [description]) and the return value (defined using @return [datatype] [description]).

You accept the file as an argument, which is the array you find in $_FILES. To process this file, you need to break the array apart into individual values. You do this using the list() function, which allows you to create named variables for each array index as a comma-separated list.

What that means becomes clear if you look at how you previously defined an array:

$array = array(
    'First value',
    'Second value'
);

The array values are separated by list() as follows:

list($first, $second) = $array;
echo $first, "<br />", $second;

This produces the following output:

First value
Second value

In processUploadedImage(), you need to pull out the five pieces of information supplied about your uploaded file. You can accomplish this by adding the following code in bold to images.inc.php:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

    /**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

        // Finish processing
    }
}

?>

8.3.1.1. Checking for Errors Using Exceptions

Next, you need to check whether there an error occurred during the upload. When you're dealing with files uploaded through an HTML form, you have access to a special constant called UPLOAD_ERR_OK that tells you whether a file uploaded successfully.

Before you try to process the file, you need to make sure that your $err value is equivalent to UPLOAD_ERR_OK. Add the following code in bold to images.inc.php:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

    /**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

        // If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            return;
        }

        // Finish processing
    }
}

?>

Note the use of the throw new Exception() line. This is a special form of error handling available in object-oriented scripts. Exceptions give you the ability to catch errors in your scripts without displaying ugly error messages to your end user.

In its simplest form, an exception can return a custom error message to your user in the event of an error. The preceding script takes this approach. Passing a string as an argument to the exception lets you define a custom error message; I'll cover how you handle Exceptions in a moment.

8.3.1.2. Saving the File

So far you can determine whether your file was uploaded without error; the next step is to save an uploaded file to your file system. Begin by assigning a path and filename to save. For now, you can use the original name of the file, which you can grab from $save_dir of the instantiated object. This is the path that displays the image.

However, you still don't have enough information to save your file. Specifically, your site isn't stored at the web root, so you need to get the root path of your site to generate an absolute path, which you can use to save the file. Fortunately, the $_SERVER superglobal stores the document root path for you, so all you need to do is include its value to save the image.

You can establish your paths by adding the code in bold to images.inc.php:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

    /**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

        // If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            exit;
        }

        // Create the full path to the image for saving
        $filepath = $this->save_dir . $name;

// Store the absolute path to move the image
        $absolute = $_SERVER['DOCUMENT_ROOT'] . $filepath;

        // Finish processing
    }
}

?>

For example, if your $filepath is simple_blog/images/IMG001.jpg, your $absolute value might be /Applications/XAMPP/xamppfiles/htdocs/simple_blog/images/IMG001.jpg.

Now that you have your paths on hand, you can save your image to the file system. Do this using the move_uploaded file() function, which accepts two arguments: the temporary location of an uploaded file and the location where that file should be saved permanently.

You can save your image by adding the following code in bold to images.inc.php:

<?php

class ImageHandler
{
    // The folder in which to save images
    public $save_dir;

    // Sets the $save_dir on instantiation
    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

    /**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

        // If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            exit;
        }

// Create the full path to the image for saving
        $filepath = $this->save_dir . '/' . $name;

        // Store the absolute path to move the image
        $absolute = $_SERVER['DOCUMENT_ROOT'] . $filepath;

        // Save the image
        if(!move_uploaded_file($tmp, $absolute))
        {
            throw new Exception("Couldn't save the uploaded file!");
        }

        return $filepath;
    }
}

?>

At this point, you're ready to try out your class!

8.3.2. Modifying update.inc.php to Save Images

You've put your class together; next, you need to instantiate it in update.inc.php and feed it your uploaded image.

You can do this by opening update.inc.php and modifying it so it contains the lines in bold:

<?php

// Include the functions so you can create a URL
include_once 'functions.inc.php';

// Include the image handling class
include_once 'images.inc.php';

if($_SERVER['REQUEST_METHOD']=='POST'
    && $_POST['submit']=='Save Entry'
    && !empty($_POST['page'])
    && !empty($_POST['title'])
    && !empty($_POST['entry']))
{
    // Create a URL to save in the database
    $url = makeUrl($_POST['title']);

if(isset($_FILES['image']['tmp_name']))
    {
        try
        {
            // Instantiate the class and set a save path
            $img = new ImageHandler("/simple_blog");

            // Process the file and store the returned path
            $img_path = $img->processUploadedImage($_FILES['image']);

            // Output the uploaded image as it was saved
            echo '<img src="', $img_path, '" /><br />';
        }
        catch(Exception $e)
        {
            // If an error occurred, output your custom error message
            die($e->getMessage());
        }
    }
    else
    {
        // Avoids a notice if no image was uploaded
        $img_path = NULL;
    }

    // Outputs the saved image path
    echo "Image Path: ", $img_path, "<br />";
    exit; // Stops execution before saving the entry

    // Include database credentials and connect to the database
    include_once 'db.inc.php';
    $db = new PDO(DB_INFO, DB_USER, DB_PASS);

The most important task this code accomplishes is to make your ImageHandler class available by including images.inc.php. Next, you need to check whether an image was uploaded, which you accomplish by making sure the temporary file exists.

8.3.2.1. Using try...catch with Exceptions

Your next task introduces a new construct: the try...catch statement. You use this construct with exceptions to handle errors gracefully. Essentially it says: "Run this snippet of code. If an exception is thrown, catch it and perform the following snippet."

In your image handling snippet, you place your object instantiation and processing within a try block. Doing so ensures that you can output the custom error message by grabbing the message within the catch block if any of the custom errors you define within the class are thrown.

Inside your try block, you instantiate the ImageHandler object and pass "/simple_blog" as its argument, which sets the $save_dir property. Next, you call processUploadedImage() and pass the uploaded file as its argument. processUploadedImage() returns the file's path, so you store that in a variable called $img_path, which you use (for now) to output the image for viewing via an <img> HTML tag.

Finally, you output the image path as plain text and exit the script to prevent your test entries from being saved to the database.

You can test your class by uploading an image. Navigate to your admin form in a browser and fill out the form with test data (you don't save this), then select a file to upload. When you press the Save Entry button, you should see your image displayed, along with its path (see Figure 8-2).

Figure 8.2. An image uploaded by your ImageHandler class

If you look at your simple_blog folder in the file system, you'll see that the image has been saved (see Figure 8-3).

Figure 8.3. The uploaded image saved in the simple_blog folder

This isn't necessarily bad, but it does cause clutter in your folder. You can clear this up by creating a new folder to store images in.

8.3.3. Creating a New Folder

You could simply create the folder manually, but it's better to use PHP to check whether a folder exists, then create it if it doesn't. This way, you have to change only the path if the folder you wish to save images in changes in the future; the alternative is to go in and manipulate the file system directly.

You can make a new folder by creating a new method in your ImageHandler class called checkSaveDir() that creates a directory if it doesn't exist already.

Begin by declaring your method in ImageHandler. This method is private, so you want to make sure you control its use. In images.inc.php, after processUploadedImage(), define your new method by adding the code in bold:

<?php

class ImageHandler
{
    public $save_dir;

    public function __construct($save_dir)
    {
        $this->save_dir = $save_dir;
    }

/**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file, $rename=TRUE)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

        // If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            exit;
        }

        // Create the full path to the image for saving
        $filepath = $this->save_dir . $name;

        // Store the absolute path to move the image
        $absolute = $_SERVER['DOCUMENT_ROOT'] . $filepath;

        // Save the image
        if(!move_uploaded_file($tmp, $absolute))
        {
            throw new Exception("Couldn't save the uploaded file!");
        }

        return $filepath;
    }

    /**
     * Ensures that the save directory exists
     *
     * Checks for the existence of the supplied save directory,
     * and creates the directory if it doesn't exist. Creation is
     * recursive.
     *
     * @param void
     * @return void
     */

private function checkSaveDir()
    {
        // Check for the dir
    }

}

?>

You've declared your method. Next you need to figure out which path to check. As with processUploadedImage(), you use the $_SERVER superglobal and your $save_dir property to create your path to check. The only difference is that you don't attach a file name this time.

Add the lines in bold to checkSaveDir() to store your path to check:

/**
     * Ensures that the save directory exists
     *
     * Checks for the existence of the supplied save directory,
     * and creates the directory if it doesn't exist. Creation is
     * recursive.
     *
     * @param void
     * @return void
     */
    private function checkSaveDir()
    {
        // Determines the path to check
        $path = $_SERVER['DOCUMENT_ROOT'] . $this->save_dir;

        // Check for the dir
    }

Next, you need to see whether the $path you've stored exists. PHP provides a function to do exactly this in is_dir(). If the path exists, is_dir() returns TRUE; otherwise, it returns FALSE. You want to continue processing only if the directory doesn't exist, so add a check to see whether is_dir() returns FALSE before continuing. Insert the lines in bold into checkSaveDir():

/**
     * Ensures that the save directory exists
     *
     * Checks for the existence of the supplied save directory,
     * and creates the directory if it doesn't exist. Creation is
     * recursive.
     *
     * @param void
     * @return void
     */
    private function checkSaveDir()
    {
        // Determines the path to check
        $path = $_SERVER['DOCUMENT_ROOT'] . $this->save_dir;

        // Checks if the directory exists
        if(!is_dir($path))
        {
            // Create the directory
        }
    }

If the directory doesn't exist, you need to create it. You accomplished in PHP with the mkdir() function, which translates in plain English to make directory. You need to pass three arguments to mkdir() for it to work properly: the path, the mode, and a value that indicates whether directories should be created recursively.

The first argument, the path, is what you just created and stored in the $path variable.

The second argument, the mode, describes how to set the folder permissions. The default mode is 0777, which provides the widest possible access. Your image files are not sensitive, so you display them to any user viewing your page.

NOTE

For more information on file permissions, check the PHP manual entry on chmod()at http://php.net/chmod. Basically, you set folder permissions using an octal number, where each number represents who can access the file (owner, owner's group, and everyone else). Each number represents a level of permission, with 7 being the highest (read, write, and execute).

Your third argument is a boolean value that tells the function whether directories should be created recursively. Only one directory at a time can be created when set to FALSE (the default). This means you need to call mkdir() twice if you want to add two subdirectories to the simple_blog folder with the path, simple_blog/images/uploads/. If you set the third argument to TRUE, however, you can create both directories with a single function call.

You need to control access to this method, so you allow the function to create directories recursively.

You can create the directory in the simple_blog folder by adding the lines in bold to checkSaveDir():

/**
     * Ensures that the save directory exists
     *
     * Checks for the existence of the supplied save directory,
     * and creates the directory if it doesn't exist. Creation is
     * recursive.
     *
     * @param void
     * @return void
     */
    private function checkSaveDir()
    {
        // Determines the path to check
        $path = $_SERVER['DOCUMENT_ROOT'] . $this->save_dir;

        // Checks if the directory exists
        if(!is_dir($path))
        {
            // Creates the directory
            if(!mkdir($path, 0777, TRUE))
            {
                // On failure, throws an error
                throw new Exception("Can't create the directory!");
            }
        }
    }

This code includes a provision to throw an exception if mkdir() returns FALSE, which means it failed. Your method is finally ready. You want to call it from the processUploadedImage() method before you attempt to move the uploaded file. Implement this by adding the lines in bold lines to processUploadedImage():

/**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

// If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            exit;
        }

        // Check that the directory exists
        $this->checkSaveDir();

        // Create the full path to the image for saving
        $filepath = $this->save_dir . $name;

        // Store the absolute path to move the image
        $absolute = $_SERVER['DOCUMENT_ROOT'] . $filepath;

        // Save the image
        if(!move_uploaded_file($tmp, $absolute))
        {
            throw new Exception("Couldn't save the uploaded file!");
        }

        return $filepath;
    }

It's time to test your new function. In update.inc.php, modify your object instantiation to use this path as the $save_dir: /simple_blog/images/.

Your code should look like this:

<?php

// Include the functions so you can create a URL
include_once 'functions.inc.php';

// Include the image handling class
include_once 'images.inc.php';

if($_SERVER['REQUEST_METHOD']=='POST'
    && $_POST['submit']=='Save Entry'
    && !empty($_POST['page'])
    && !empty($_POST['title'])
    && !empty($_POST['entry']))
{
    // Create a URL to save in the database
    $url = makeUrl($_POST['title']);

if(isset($_FILES['image']['tmp_name']))
    {
        try
        {
            // Instantiate the class and set a save path
            $img = new ImageHandler("/simple_blog/images/");

            // Process the file and store the returned path
            $img_path = $img->processUploadedImage($_FILES['image']);

            // Output the uploaded image as it was saved
            echo '<img src="', $img_path, '" /><br />';
        }
        catch(Exception $e)
        {
            // If an error occurred, output your custom error message
            die($e->getMessage());
        }
    }
    else
    {
        // Avoids a notice if no image was uploaded
        $img_path = NULL;
    }

    // Outputs the saved image path
    echo "Image Path: ", $img_path, "<br />";
    exit; // Stops execution before saving the entry

    // Include database credentials and connect to the database
    include_once 'db.inc.php';
    $db = new PDO(DB_INFO, DB_USER, DB_PASS);

Save update.inc.php and navigate to your admin form in a browser, then fill out the form and submit an image. After you click the Save Entry button, you should see the image you uploaded previously, as well as its path; your script places the image in the newly created images folder (see Figure 8-4).

Figure 8.4. The image uploaded shows that it's been stored in the new images folder.

You can check the file system manually to see your new folder and the saved, uploaded image (see Figure 8-5).

Figure 8.5. The images folder has been created, and the image has been saved in it.

You're almost ready to start working with the database. First, however, you need to make sure that your images have unique names, so you don't accidentally overwrite older uploads with new ones.

8.3.4. Renaming the Image

You can't trust that every file uploaded to your blog will be uniquely named, so you need to rename any image that is uploaded to your blog. Otherwise, it's possible for a user who uploads an image named to overwrite that image later if he submits a future image with the same name. In this case, the new image would suddenly appear for the older entry, as well as the new one, and you would lose the old image.

You can avoid this by creating a new private method in ImageHandler that generates a new, unique name for any uploaded image. You can make sure this name is unique by using the current timestamp and a random four-digit number between 1000 and 9999. This way, even images uploaded during the same second will receive unique names.

NOTE

This method is not 100% effective, but the likelihood of two images being uploaded at the exact same second and generating the exact same random number is so slim that it will most likely never be a problem.

This method is a one-liner that accepts one argument: the file extension you want to use (we'll get to how you know what file extension to send in just a moment). The method returns the current timestamp, an underscore, a random number, and a file extension.

It's time to add your method to ImageHandler by inserting the code in boldafter processUploadedImage() in images.inc.php:

/**
     * Generates a unique name for a file
     *
     * Uses the current timestamp and a randomly generated number
     * to create a unique name to be used for an uploaded file.
     * This helps prevent a new file upload from overwriting an
     * existing file with the same name.
     *
     * @param string $ext the file extension for the upload
     * @return string the new filename
     */
    private function renameFile($ext)
    {
        /*
         * Returns the current timestamp and a random number
         * to avoid duplicate filenames
         */
        return time() . '_' . mt_rand(1000,9999) . $ext;
    }

8.3.4.1. Determining the File Extension

Before renameFile() can work, you need to figure out the uploaded image's extension. Do this by accessing the image's type with the value stored in $type in processUploadedImage().

All uploaded files have a content type, which you can use to determine the proper file extension to use. You need to make sure you're processing only images, so you use a switch and match the known content types you want to accept, then set a default action to throw an error if an unexpected content type is passed.

The content types you want to accept are:

  • image/gif: A GIF image (.gif)

  • image/jpeg: A JPEG image (.jpg)

  • image/pjpeg: A JPEG image as it is recognized by certain browsers, which uses the same file extension as a "normal" JPEG (.jpg)

  • image/png: A PNG image (.png)

To check for content types, you create a new private method called getImageExtension(). This method contains the switch just discussed and returns the proper file extension. Add the following code in bold to images.inc.php just after renameFile():

/**
     * Determines the filetype and extension of an image
     *
     * @param string $type the MIME type of the image
     * @return string the extension to be used with the file
     */
    private function getImageExtension($type)
    {
        switch($type) {
            case 'image/gif':
                return '.gif';

            case 'image/jpeg':
            case 'image/pjpeg':
                return '.jpg';

            case 'image/png':
                return '.png';

            default:
                throw new Exception('File type is not recognized!'),
        }
    }

Now you can retrieve the file extension to pass to renameFile(), which means you're ready to implement the methods in processUploadedImage().

There might be a point at which you no longer wish to rename files, so you should add an argument to processUploadedImage() that holds a boolean value. If set to TRUE (the default), the image is renamed; if set to FALSE, the original filename is used.

You can add file renaming to your method by adding the following code in bold to processUploadedImage():

/**
     * Resizes/resamples an image uploaded via a web form
     *
     * @param array $upload the array contained in $_FILES
     * @param bool $rename whether or not the image should be renamed
     * @return string the path to the resized uploaded file
     */
    public function processUploadedImage($file, $rename=TRUE)
    {
        // Separate the uploaded file array
        list($name, $type, $tmp, $err, $size) = array_values($file);

// If an error occurred, throw an exception
        if($err != UPLOAD_ERR_OK) {
            throw new Exception('An error occurred with the upload!'),
            exit;
        }

        // Check that the directory exists
        $this->checkSaveDir();

        // Rename the file if the flag is set to TRUE
        if($rename===TRUE) {
            // Retrieve information about the image
            $img_ext = $this->getImageExtension($type);

            $name = $this->renameFile($img_ext);
        }

        // Create the full path to the image for saving
        $filepath = $this->save_dir . $name;

        // Store the absolute path to move the image
        $absolute = $_SERVER['DOCUMENT_ROOT'] . $filepath;

        // Save the image
        if(!move_uploaded_file($tmp, $absolute))
        {
            throw new Exception("Couldn't save the uploaded file!");
        }

        return $filepath;
    }

Save images.inc.php and try uploading another image through the admin form. You should see a renamed file stored in the file system, as well as on your screen (see Figure 8-6).

Figure 8.6. An image renamed by your script

Your ImageHandler class now accepts, renames, and stores images in your file system, which means you're ready to start working with the database.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.126.211