views:

144

answers:

1

Hello! My first question is basically asking for a code-review. Does the code I'm about to provide use a Factory to promote Polymorphism? Its written in PHP. Here are the basic requirements:

  • Pass a long url to a library and return a shortened url. Along with the long url, pass user properties to attempt to locate the users specific shortener service and API key.
  • Allow users to set API keys for specific URL shorteners. My code assumes this is already set in the database and Bitly is the only service supported.
  • If a user doesn't have an API key and service set, use the default API key and service. Again, my code assumes the default is set to Bitly in the database.
  • If the url shortener service fails, log the failure, but don't throw an exception. The library should silently fail. Instead of using the short url, we'll use the long url.

Here is an example of calling the code:

<?php
$long_url = 'http://www.news.com/story/1';
$account_id = 1;

$use_this_url = $long_url;

$meta = array(
    'account_id' => $account_id,
    // OPTIONS
    // 'service_id' => $service_id,
    // 'account_id' => $account_id,
);
$shortener = new Shortener_Factory($long_url, $meta);
if ($shortener->shorten_long_url() AND $shortener->save_short_url())
{
                $use_this_url = $shortener->short_url;   
}

echo $use_this_url;

Here are the classes:

<?php
interface ShortenerServiceInterface {
    public function save_short_url();
    public function shorten_long_url();
}

abstract class ShortenerServiceAbstract implements ShortenerServiceInterface {

    // Url to be shortened
    public $long_url = '';

    // Long url unique id
    public $url_id = 0;

    // Service unique id
    public $shorturlservice_id = 0;

    // Service account unique id
    public $shorturlserviceaccount_id = 0;    

    // Short url service unique API login
    public $api_login = '';

    // Short url service unique API key
    public $api_key = '';

    // Short url service unique hash which maps to original url value
    public $hash = '';

    // Shortened url string
    public $short_url = '';


    // Attempt to call shortner service three times before failing
    public $attempts = 3;

    // Shorten long url with specific service API/logic
    public function shorten_long_url()
    {
        // Can't save a short url when one doesn't exist
        if (!$this->long_url OR !$this->api_login OR !$this->api_key) {
            log('error', 'ShortenerServiceAbstract::shorten_long_url - no long url to shorten - '.var_export($this, TRUE));
            return FALSE;
        }        
    }

    // Save short url and related meta-data to shorturls table
    public function save_short_url()
    {
        // Can't save a short url when one doesn't exist
        if (!$this->url_id OR !$this->hash OR !$this->shorturlservice_id OR !$this->shorturlserviceaccount_id) {
            log('error', 'ShortenerServiceAbstract::save_short_url - no short url to save - '.var_export($this, TRUE));
            return FALSE;
        }

        // Insert a new short url, or update an existing record
        $saved = Shorturl_Model::insert_on_dup_key_update($this->url_id, $this->hash, $this->shorturlservice_id, $this->shorturlserviceaccount_id);

        if (!$saved) {
            log('error', 'ShortenerServiceAbstract::save_short_url - short url record can not be saved - '.var_export($this, TRUE));
            return FALSE;
        } else {
            return TRUE;
        }        
    }

}

// Bitly, a simple url shortener
// @link http://code.google.com/p/bitly-api/wiki/ApiDocumentation
class ShortenerServiceBitly extends ShortenerServiceAbstract {

    public function shorten_long_url()
    {
        // Make sure we have required members set
        parent::shorten_long_url();

        $urlencoded = urlencode($this->long_url);
        $bitlyurl = 'http://api.bit.ly/shorten?version=2.0.1&amp;longUrl='.$urlencoded.'&amp;login='.$this-&gt;api_login.'&amp;apiKey='.$this-&gt;api_key.'&amp;history=1';

        $attempts = 1;
        while ($attempts <= 3) {
            $json_result = file_get_contents($bitlyurl);     
            if ($json_result) {                                 
                // Return an assoc array
                $json_decode =    json_decode($json_result, TRUE);
                if (is_array($json_decode) AND isset($json_decode['errorCode']) AND $json_decode['errorCode'] == 0) {
                    // Don't compare sent URL with returned URL
                    // Bitly removes invalid poritions of URLs
                    // The camparison might fail even though the URLs are the "same"
                    $shortened = current($json_decode['results']);
                    break;
                } else {
                    log('error', 'ShortenerServiceBitly::shorten_long_url - bit.ly json decoded - '.var_export($json_decode, TRUE));
                }
            } else {
                    log('error', 'ShortenerServiceBitly::shorten_long_url - bit.ly http response - '.var_export($json_result, TRUE));
            }
            $attempts++;
        }

        if (isset($shortened)) {
            $this->short_url = $shortened['shortUrl'];
            $this->hash = $shortened['userHash'];
            return TRUE;
        } else {
            return FALSE;
        }
    }

}

// Shortener Factory
class Shortener_Factory {

    // Shortener service account parameter object
    // @param object shortener account properties
    private $_account;

    // Shortener service object created by factory
    //@param object shorterner service functions
    private $_service; 

    // Url to be shortened
    private $_long_url;

    // Set url members, service parameter object and finally the service itself 
    public function __construct($long_url, $meta=array())
    {
        $this->_long_url = $long_url;

        $this->_set_account($meta);
        $this->_set_service();                
    }

    // Set shortener service account parameter object
    // @param $meta array determine parameters for the current service object
    private function _set_account($meta=array())
    {                    
        $s = FALSE;

        // Get shorturl service account
        if (isset($meta['account_id'])) {
            $s = Shorturlserviceaccount_Model::get_by_account_id($meta['account_id']);
        } elseif (isset($meta['service_id'])) {
            $s = Shorturlserviceaccount_Model::get_by_service_id($meta['service_id']);
        }

        // Account not found, lets use default
        if (!$s) {
            $s = Shorturlserviceaccount_Model::get_default();
        }

        // Must have a service to use
        if ($s === FALSE) {
            log('error', 'Shortener_Core::_set_account - _account not found - '.var_export($this, TRUE));
            return FALSE;
        } else {
            $this->_account = $s;
        }        
    }

    // Use shortener service account parameter object to set shortener service object
    private function _set_service()
    {
        switch ($this->_account->name) {
            case 'bitly':
                $this->_service = new ShortenerServiceBitly;
    break;
            default:
                log('error', 'Shortener_Core::_set_service - _account not set - '.var_export($this, TRUE));
    return FALSE;
        }

        $this->_service->long_url = $this->_long_url;        
        $this->_service->shorturlserviceaccount_id = $this->_account->id;    
        $this->_service->shorturlservice_id = $this->_account->shorturlservice_id;
        $this->_service->api_login = $this->_account->api_login;
        $this->_service->api_key = $this->_account->api_key;
    }

    // Public API for shortener service object methods
    public function __call($name, $arguments)
    {
        if (!$this->_service) {
                log('error', 'Shortener_Core::__call - _service not set - '.var_export($this, TRUE));
    return FALSE;            
        }
        return $this->_service->$name();
    }

    // Public API for shortener service object members
    public function __get($name)
    {
        return ($this->_service->$name) ? $this->_service->$name : NULL;
    }
}
A: 

The job of the factory pattern is to abstract away the creation of objects. The reason this is useful is because the way in which objects are created may not the same as just:

$instance = new Object();

any time you create it. For example if you first need to deal with loading an include file or you need to pick one of a few derived classes based on some parameter that is not known before runtime.

A factory can be as simple as something like:

function getInstance($objectType, $params)
{
    if (!class_exists($objectType)) {
        throw new Exception('Bad class');
    }
    $instance = new $objectType($params);
    return $instance;
}

Or can be as complex as you like, but these are the basic rules to follow. check out the wikipedia article here for a PHP example

Neel