Error Handling for Dynamic Twitter Signature Images with PHP (
Page 1 of 4 )
Over the course of my past two articles I’ve been showing you how to build a script capable of creating and displaying a dynamic signature image containing the latest status from a user’s Twitter feed. In the third installment in this series, I will be demonstrating how to add proper object-oriented error handling to the SignatureImage class.Before we begin, let’s take a moment to examine the fully operational class.
class SignatureImage
{
private $screen_name;
private $profile_image;
private $status_text;
private $local_avatar;
public function __construct($name, $bg_image, $adir)
{
$this->fetchUserInfo(strtolower($name));
$this->fetchAvatar($this->profile_image, $adir);
$this->renderImage();
}
private function fetchUserInfo($name)
{
$url = "http://twitter.com/statuses/user_timeline/{$name}.xml?count=1";
$xml = $this->curlRequest($url);
if ($xml === false) {
// User feed unavailable.
}
$statuses = new SimpleXMLElement($xml);
if (!$statuses || !$statuses->status) {
// 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')) {
// 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')) {
// No GD support.
}
$margin_left = 11;
$margin_bottom = 6;
$image = @imagecreatefromjpeg($bg_image);
if (!$image) {
// 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) {
// 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) {
// 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');
This class already employs pretty thorough error-catching. Throughout the code I’ve used comments to indicate error states. Today we’ll be replacing those comments with actual error-handling code in order to make this a fully functional OOP class.