A Custom Exception Class for Dynamic Twitter Signature Images with PHP

Welcome to part four of a five-part series on creating a dynamic Twitter signature image in PHP. In the last segment, I showed you how to implement PHP 5 exceptions as an error-handling mechanism in your signature image application. Today we’re going to expand upon that concept by creating a custom exception class for handling error states in our application.

The code you have for this application so far is fully functional and includes full error-handling.  You may be wondering what the need is for a custom exception class in the first place.  So I’d like to take a moment to discuss why I’ve chosen to create one.

This application serves a single purpose. It accepts an input and delivers an image to the browser.  In the event of an error, the script halts execution and displays that error. 

The problem lies in this difference in output. 

The calling page is expecting an image and therefore won’t display the error text.  So to make this useful, it might be a good idea to deliver the error text as an image instead. 

Since this only needs to be done in the event an exception occurs in our application, it makes sense that the code to produce this image should reside in a custom exception.  This also provides an opportunity to explore a little deeper into PHP 5’s error handling capabilities.

For those readers that are just now tuning in, let’s take a look at the code we have so far.  On the next page you’ll see the SignatureImage class in its entirety.

{mospagebreak title=Examining the SignatureImage class}

class SignatureImage

{

    private $screen_name;

    private $profile_image;

    private $status_text;

    private $local_avatar;

   

    public function __construct($name, $bg_image, $adir)

    {

        try {

            $this->fetchUserInfo(strtolower($name));

            $this->fetchAvatar($this->profile_image, $adir);

            $this->renderImage();

        }

        catch (Exception $e) {

            die($e->getMessage);

        }

    }

 

    private function fetchUserInfo($name)

    {

        $url = "http://twitter.com/statuses/user_timeline/{$name}.xml?count=1";

        $xml = $this->curlRequest($url);

        if ($xml === false) {

            throw new Exception(‘User feed unavailable.’);

        }

        $statuses = new SimpleXMLElement($xml);

        if (!$statuses || !$statuses->status) {

            throw new Exception(‘Invalid user channel.’);

        }

       foreach ($statuses->status as $status) {

            $this->status_text   = (string) $status->text;

            $this->profile_image = (string) $status->user->profile_image_url;

            $this->screen_name   = (string) $status->user->screen_name;

            break;

        }

    }

 

    private function curlRequest($url)

    {

        if (!extension_loaded(‘curl’)) {

            throw new Exception(‘PHP extension CURL is not loaded.’);        }

        $curl = curl_init($url);

        curl_setopt($curl, CURLOPT_HEADER, false);

        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

        $result = curl_exec($curl);

        if (curl_errno($curl) !== 0 || curl_getinfo($curl, CURLINFO_HTTP_CODE) !== 200) {

            $result === false;

        }

        curl_close($curl);

        return $result;

    }

 

    private function fetchAvatar($url, $adir)

    {

        $parts      = explode(‘/’, $url);

        $fname      = end($parts);

        $adir       = preg_match(‘#^(.*?)/$#i’, $url) ? $adir : "{$adir}/";

        $fname      = $adir . $fname;

        if (!file_exists($fname)) {

            $img = $this->curlRequest($url);

            $fp = fopen($fname, ‘w’);

            fwrite($fp, $img);

            fclose($fp);

        }

        $this->local_avatar = $fname;

    }

}

 

    private function renderImage($bg_image)

    {

        if (!function_exists(‘gd_info’)) {

            throw new Exception(‘No GD support.’);

        }

        $margin_left = 11;

        $margin_bottom = 6;

        $image = @imagecreatefromjpeg($bg_image);

        if (!$image) {

            throw new Exception(‘Unable to create image.’);

        }

        $this->embedAvatar($image, $this->local_avatar, $margin_left, $margin_bottom);

        $string = "{$this->screen_name}: {$this->status_text}";

        $this->embedText($image, $string, $margin_left, $margin_bottom);

        header("Content-type: image/jpeg");

        $created = @imagejpeg($image);

        if (!$created) {

            throw new Exception(‘Unable to finalize image.’);

        }

        @imagedestroy($image);

    }

 

    private function embedAvatar(&$image, $avatar, $left, $bottom)

    {

        if (file_exists($avatar)) {

            if (stristr($avatar, ‘.jpg’)) {

                $aimg = @imagecreatefromjpeg($avatar);

            } elseif (stristr($avatar, ‘.png’)) {

                $aimg = @imagecreatefrompng($avatar);

            }

            if (!$aimg) {

                throw new Exception(‘Unable to create avatar.’);

            }

            $awidth  = imagesx($aimg);

            $aheight = imagesy($aimg);

            $top     = imagesy($image) – $aheight – $bottom;

            $copied  = @imagecopy($image, $aimg, $left, $top, 0, 0, $awidth, $aheight);

            if (!$copied) {

            // Unable to add avatar to image.

            }

        }

    }

 

    private function embedText(&$image, $text, $left, $top, $tmaxw=300, $font=3)

    {

        $fheight = imagefontheight($font);

        $color   = imagecolorallocate($image, 100, 100, 100);

        $lines   = $this->lineWrap($font, $text, $tmaxw);

        $lmax    = floor(imagesy($image) / $fheight);

        $lcount  = (count($lines) > $lmax) ? $lmax : count($lines);

        $ttop    = (imagesy($image) – ($lcount*$fheight)) / 2;

        $tleft   = 2*$left + 48;

        while ((list($num, $line) = each($lines)) && $num < $lcount) {

            imagestring($image, $font, $tleft, $ttop, $line, $color);

            $ttop += $fheight;

        }

    }

 

    private function lineWrap($font, $text, $maxwidth)

    {

        $fwidth  = imagefontwidth($font);

        if ($maxwidth != NULL) {

            $maxcharsperline = floor($maxwidth / $fwidth);

            $text = wordwrap($text, $maxcharsperline, "n", 1);

        }

        return explode("n", $text);

    }

}

 

isset($_GET['name']) or die(‘You must provide a user name.’);

 

new SignatureImage($_GET['name'], ‘banners/my_banner.jpg’, ‘avatars’, ‘cache’);

{mospagebreak title=Building a custom exception interface}

To begin piecing together a custom exception, you’ll first need to understand how the native Exception class is constructed.  For that you’ll need to sit down and examine the PHP documentation, so I’ll save you a little time.

interface ExceptionInterface

{

    public function __construct($message = null, $code = 0);

   

    public function getMessage();        // message of exception

    public function getCode();           // code of exception

    public function getFile();           // source filename

    public function getLine();           // source line

    public function getTrace();          // an array of the backtrace()

    public function getTraceAsString();  // formatted string of trace

   

    public function __toString();        // formatted string for display

}

In the code above I’ve created an interface that lists all of the methods provided by the native Exception class.  By implementing this interface in our own custom exception class, we’ll have a framework to ensure that our own exception is constructed in the same manner as the original. 

In larger applications, this allows multiple developers to implement any custom exception without the need for additional documentation.  That’s an important time-saver any time you have more than one developer collaborating on a project.

Now that we’ve got an interface to define our custom exceptions, we can begin putting the code together to make it work.

{mospagebreak title=Building the custom exception}

We’re going to build a custom exception called SignatureImageException.  Every error in our SignatureImage class will throw this same type of exception.  In larger applications, you’d most likely have a unique exception for each type of error thrown by a particular class.  Due to the small footprint of this application, it’s overkill to have so many different exceptions that will inherently perform the same task.

final class SignatureImageException extends Exception implements ExceptionInterface

{

We begin building our custom exception class by writing its declaration.  Notice that this exception class will be extending the native Exception class (meaning that it will inherit all of its functionality) and that it also implements the interface we’ve just created.  This provides a level of continuity by ensuring that our class cannot be created without all of the required parts.  I’ve also gone one step farther and declared this as a final class, since our application won’t be extending upon it.

    protected $message = ‘Unknown exception’;

    protected $code = 0;

The next step is to add the necessary class properties.  These are the same properties used by the native Exception class.  The $message property will contain a text string containing the exception message and the $code property will contain a user-supplied error code as an integer.

    public final function __construct($message = null, $code = 0)

    {

        if (!$message) {

            throw new SignatureImageException(‘Unknown exception’);

        }

        parent::__construct($message, $code);

    }

After defining the properties for our class, we’ll want to override the native Exception class’s constructor with one of our own.  In the native Exception class, all of the constructor’s parameters are optional.  We’re overriding that with a constructor declaration that makes the message and code parameters required.

Next, you’ll see a simple If block.  Here I’m checking that the message parameter contains a usable value.  If it’s null or an empty string, the SignatureImageException is re-thrown with the message “Unknown exception.”  This ensures that the image produced by our custom exception class will always have a visible message for the end-user.

Finally, we make a static call to the Exception class’s constructor.  This ensures that all information is preserved and correctly passed to all parent objects.  It also executes the native code that properly assigns all of the internal properties for our custom exception class.  No need to write the code ourselves!

    public final function getImage()

    {

        if (!function_exists(‘gd_info’)) {

            throw new Exception(‘No GD support’);

        }

        $image = @imagecreatefromjpeg(‘banners/my_banner.jpg’);

        if (!$image) {

            $image = @imagecreate(380, 60);

            if (!$image) {

                throw new Exception(‘Cannot create image’);

            }

            $bg    = imagecolorallocate($image, 190, 240, 250);

        }

        $line    = parent::getMessage();

        $font    = 3;

        $fwidth  = imagefontwidth($font);

        $fheight = imagefontheight($font);

        $color   = imagecolorallocate($image, 100, 100, 100);

        $ttop    = (imagesy($image) – $fheight) / 2;

        $tleft   = (imagesx($image) – ($fwidth*strlen($line))) / 2;

        imagestring($image, $font, $tleft, $ttop, $line, $color);

        header("Content-type: image/jpeg");

        $created = @imagejpeg($image);

        if (!$created) {

            throw new Exception(‘Unable to finalize image.’);

        }

        @imagedestroy($image);

    }

}

Now we wrap up our custom exception class by creating a method that will produce and output an image containing our error message.  Since the majority of this code has been covered in the previous articles in this series, I won’t go into great detail about its inner workings. 

I do want you to notice, however, the manner that I’ve used to handle errors in this portion of the code.  To this point, our custom exception relies on the fact that it creates and outputs an image.  If this code fails for any reason, the whole concept behind this application fails with it. 

For that reason, any errors produced while attempting to create the image will be thrown as general Exceptions which don’t rely on the need to create an image for the error.  This provides an excellent example of a situation in which a custom exception is better re-thrown as a lower level exception within an application.

{mospagebreak title=Implementing the custom exception}

In order to implement our newly-created custom exception, we’ll need to revisit the SignatureImage class that we built over the past few articles.  Throughout that code, we you’ll see instances of errors being thrown as shown below.

        $url = "http://twitter.com/statuses/user_timeline/{$name}.xml?count=1";

        $xml = $this->curlRequest($url);

        if ($xml === false) {

            throw new Exception(‘User feed unavailable.’);

        }

Implementing our new custom exception is as simple as editing the line that throws a native Exception and updating it with our custom exception instead.

        $url = "http://twitter.com/statuses/user_timeline/{$name}.xml?count=1";

        $xml = $this->curlRequest($url);

        if ($xml === false) {

            throw new SignatureImageException(‘User feed unavailable.’);

        }

And there you have it!  Now instead of receiving error messages as text in the browser, they are built into an image to maintain compatibility with the expected output of our SignatureImage class.

Here again we part with a fully-functioning application.  But I’m coming back for one more article in this series in which I will show you how to boost the performance of this application, overcome a pitfall in the Twitter API, and add a method of monitoring your image’s analytics.  Until next time, keep coding!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan