Create a URL shortener

URL shorteners are all the rage. At their core, they have two functions. First, encode an existing complete URL. Second, decode a shortened version of a URL and redirect to the (restored) URL.

Obviously, there are a number of services already available to do this for you, but I was very interested in adapting my framework to a very different kind of application. Since the framework uses a front controller design pattern, I can put almost all the key logic in the ApplicationDelegate object.

So, the first steps are to create a database to handle the data, and an application directory to handle the logic.

psql template1 postgres
CREATE USER iwurl WITH PASSWORD 'dbpassword' CREATEDB;
\q
createdb -U iwurl --encoding UNICODE --template template0 url
cd ~/Sites/fw/install
php app_install -n URL Shortener -w /Users/markf/Sites -a url -d pgsql:url@localhost:iwurl:dbpassword

I am going to adapt the ApplicationDelegate to respond to the two different possible requests.

Database Schema

For the moment, I am going to ignore anything but the actual shortening and expanding. Eventually, the schema will include tables for managing demographics and usage.

CREATE TABLE url (
    url_id serial,
    short varchar(5),
    long text
);
ALTER TABLE url ADD CONSTRAINT pkey_url PRIMARY KEY (url_id);
cd ~/Sites/fw/install
php orm_install -d -p -n URL Shortener -w /Users/markf/Sites -a url -r url

Front Controller

The processing can be entirely handled in the ApplicationDelegate object. Whenever a request is received, the framework gives the application an opportunity to intercede. Often, this means taking an SEO-friendly URL and turning it into an application module name (sometimes with translated parameters). For this example, however, either a request is being made to shorten a URL, or to take a shortened URL and redirect to its expanded URL. This can be done by updating the modifyPageRequest() method of ApplicationDelegate.

function modifyPageRequest($url)
{
    // Performing a url shortening
    if ($url == 'shorten') return 'Shorten';

    // Redirecting to the appropriate place
    $url = UrlFactory::retrieveUrl($url, 'short');
    $destination = $url ? $url->long : APPL_ROOT_DIR . 'error.html';
    header('Location: ' . $destination);
    exit();
}

Shorten System

To shorten a URL, I need to create a new entry in the url table.

class Shorten extends IWRenderModule
{
    protected $long;

    // can be passed via post or get (for now)
    function initialize($module)
    {
        if (! $this->long = IWRequest::postValue('url'))
        {
            $this->long = IWRequest::getValue('url');
            if ($this->long) $this->long = urldecode($this->long);
        }
    }

    function output()
    {
        if (! $this->long) return ApplicationDelegate::ERROR;    // constant set to -1

        return UrlFactory::createShortenedURL($this->long);
    }
}

class UrlFactory extends DefaultUrlFactory
{
    public static function createShortenedURL($long)
    {
        // If URL already encoded, return its short version
        if ($url = UrlFactory::retrieveUrl($long, 'long')) return 'http://istrl.ws/' . $url->short;

        // Create a new URL
        $url = new Url;
        $url->setShort(IWCodeMaker::makeCode('@@@@@'))
             ->setLong($long);
        $url->insert();

        return 'http://istrl.ws/' . $url->short;
    }
}

The IWCodeMaker object (part of the Istarel Workshop Application Framework) is a handy utility object for creating codes (including temporary passwords). In this case, we're creating a five character smart alphanumeric code (the non-zero numerals and non-confusing letters).

And that is a simple implementation of a URL shortening service. Of course, there is a lot of potential work to be done, including validation of the url, preventing duplicates, implementing the whole process via RESTful services, and so on. Those will be topics of future blog entries.