In order to avoid using some external service, you might want to add a simple feature to your application to provide a URL-shortener.
The benefits are really simple… you’ll use your own domain name and while your app is around so will be your short-URL links.
Let’s define some goals:
- We’ll provide an admin interface to take a long URL and create a short version for it
- The URL’s should be pretty simple (i.e. http://www.example.com/s/d8YS)
- We’ll have a simple counter of how many times a short URL has been clicked
First, we’ll create the table to hold our short URL data:
`id` VARCHAR(36) NOT NULL COLLATE utf8_unicode_ci,
`url_id` VARCHAR(50) NULL DEFAULT NULL COLLATE utf8_unicode_ci,
`original_url` VARCHAR(50) NULL DEFAULT NULL COLLATE utf8_unicode_ci,
`count` INT(10) NULL DEFAULT NULL,
`created` DATETIME NULL DEFAULT NULL,
`modified` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
Next, let’s take a look at the ShortUrl model:
class ShortUrl extends AppModel {
public function beforeSave() {
$tries = 0;
while ($tries <= 10) {
$urlId = $this->buildShortUrl();
if(!$this->checkExisting($urlId)) {
$this->data[$this->alias]['url_id'] = $urlId;
break;
}
}
return TRUE;
}
//this function generates short ID's
//I stole from some place online
private function buildShortUrl() {
$codeset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$base = strlen($codeset);
$n = mt_rand(299, 9999999);
$converted = NULL;
while ($n > 0) {
$converted = substr($codeset, ($n % $base), 1) . $converted;
$n = floor($n / $base);
}
return $converted;
}
private function checkExisting($urlId) {
if($this->hasAny(array($this->alias . '.' . 'url_id' => $urlId))) {
return TRUE;
}
return FALSE;
}
}
?>
The model handles the short URL creation.
In beforeSave() we attempt to generate the short ID, by using the buildShortUrl(). This function works well for my app, because I do not anticipate a ton of URL’s will be required and the ID’s produced are pretty simple, as seen in the example.
Also, because the ID is quite simple there is a chance of collision, so I attempt to check for uniqueness 10 times (as seen in the while loop). Again, this works well for my needs, but you might want to adjust the tries for your app… or perhaps replace the buildShortUrl() method to return a slightly more unique ID to begin with.
At any rate, the approach should remain the same.
Now, let’s take a look at the controller:
class ShortUrlsController extends AppController {
public function admin_index() {
$this->ShortUrl->recursive = 0;
$this->set('shortUrls', $this->paginate());
}
public function forward() {
$this->autoRender = FALSE;
$redirectTo = $this->ShortUrl->field('original_url', array('ShortUrl.url_id' => $this->params['id']));
$this->ShortUrl->updateAll(array('ShortUrl.count' => 'ShortUrl.count + 1', $this->params['id']));
$this->redirect($this->processUrl($redirectTo));
}
public function admin_add() {
if (!empty($this->data)) {
if ($this->ShortUrl->save($this->data)) {
$this->Session->setFlash(__('The Short Url has been saved', true));
$this->redirect(array('action' => 'show_url', $this->ShortUrl->id, 'admin' => true));
} else {
$this->Session->setFlash(__('The Short Url could not be saved. Please, try again.', true));
}
}
}
public function admin_show_url($id = NULL) {
if($id) {
$this->set('url', $this->ShortUrl->field('url_id', array('ShortUrl.id' => $id)));
}
}
protected function processUrl($url = NULL) {
if($url) {
if(!stristr($url, 'http://')) {
return 'http://' . $url;
}
else {
return $url;
}
}
}
}
?>
The admin_add() function is very generic, since all the logic for actually building the URL is handled by the model.
The forward() takes the short URL ID, looks up the actual (long) URL in the table, updates the counter and handles the redirect. You’ll also notice a simple method processUrl(), which handles tacking on ‘http://’ to a URL, just in case a user has entered something like: www.yahoo.com instead of http://www.yahoo.com. Of course, the ‘http://’ is required for the redirect to work properly.
Last, but not least, we require a simple route in routes.php to tie all of this together:
Router::connect(‘/s/:id’, array(‘controller’=>’short_urls’, ‘action’=>’forward’), array(‘id’=>'[0-9a-zA-Z]+’));
And there you have a simple, but effective URL-shortener that works well for any app.
P.S. Just FYI, here are the views for the “admin” actions:
admin_add()
Allows the admin to enter an original (or long) URL.
echo $form->create();
echo $form->input('ShortUrl.original_url');
echo $form->end('Build short URL');
?>
admin_show_url()
Shows the produced, short, URL back to the admin.
echo FULL_BASE_URL . '/s/' . $url;
?>