Herman J. Radtke III

Read more of my blog or subscribe to my feed.


Using Absolute URL's In The View

Written by Herman J. Radtke III on 20 Mar 2010

We recently had a project at work that involved replacing all the relative URL's from the application with absolute URL's. In the past, developers had just hard-coded an absolute URL only when they need to force the browser over to https. Now we are using multiple subdomains, so this approach is no longer sufficient. We also wanted a way to easy rotate assets through multiple CDN URL's to speed up the time it takes a user's web browser to load all the content. There are two requirements:
  1. Prefix a relative url path with a host.
  2. Rotate a set of relative url's through a given number of cdn hosts.
We currently use Zend Framework at work, so it is only logical we use as much of the framework as possible.  The straight forward approach is to extend Zend_View_Helper_Abstract and create a few functions to fill the requirements.  There are few problems with this approach.  The first is function creep.  We know we need to prefix http://www.hautelook.com and https://www.hautelook.com, but there may be more later.  We may decide to use something like https://ssl.hautelook.com or require another subdomain like http://ftp.hautelook.com.  This would require us to create another function, write some unit tests for it and finally send it through QA.  This issue is even more compounded with the cdn rotation function. Another problem is that we need absolute url's in some of our business logic.  We have webservices that serve up XML or JSON that contain locations to such as images or a catalog.  We want these services to take advantage of the absolute url logic too.  If we implement a view helper, then the service layer becomes coupled to the view in order to reuse the logic. Enter PHP 5.3, functional programming and an inspirational post from Eli White.  We can use the factory method pattern and closures to meet all the requirements and isolate the parts that change.  Read these posts if you are not familiar with functional programming in PHP.
class Crimson_Url
{
    public static function absolute($host)
    {
        return function($path) use ($host) {
            return "{$host}{$path}";
        };
    }

    public static function rotate($subdomain, $host, $rotations, $protocol='http')
    {
        $current = 0;

        return function($path) use (&$current, $subdomain, $host, $rotations, $protocol) {
            if ($current == $rotations) {
                $current = 1;
            } else {
                $current++;
            }

            return "{$protocol}://{$subdomain}{$current}.{$host}{$path}";
        };
    }
}
The absolute function demonstrates how simple, yet powerful, closures can be.  The absolute function is creating a function that concatenates to strings together: a host name and a relative url.  Here is how we would use this:
$www = Crimson_Url::absolute('http://www.example.com');
$ssl = Crimson_Url::absolute('https://www.example.com');

echo $www('/foo.jpg'), PHP_EOL;
echo $ssl('/secure.php'), PHP_EOL;
Output:
http://www.example.com/items.php
https://www.example.com/secure.php
Take this one step further and think about how you can use the HTTP_HOST or HTTP_REFERER values from $_SERVER to make absolute URL generation almost completely automatic. The rotate function is slightly more complex.  The $current variable is being declared in the factory method and passed by reference.  We must explicitly pass this by reference otherwise PHP will pass it by value and the value of $current will be 1 everytime.  Here is how we would use the rotater:
$cdn = Crimson_Url::rotate('cdn', 'hautelook.com', 3);
$sslCdn = Crimson_Url::rotate('cdn', 'hautelook.com', 3, 'https');

echo $cdn('/foo.jpg'), PHP_EOL;
echo $cdn('/bar.jpg'), PHP_EOL;
echo $cdn('/baz.jpg'), PHP_EOL;
echo $cdn('/bob.jpg'), PHP_EOL;

echo $sslCdn('/foo.jpg'), PHP_EOL;
echo $sslCdn('/bar.jpg'), PHP_EOL;
Output:
http://cdn1.hautelook.com/foo.jpg
http://cdn2.hautelook.com/bar.jpg
http://cdn3.hautelook.com/baz.jpg
http://cdn1.hautelook.com/bob.jpg
https://cdn1.hautelook.com/foo.jpg
https://cdn2.hautelook.com/bar.jpg
Notice how there are no problems with static variable conflicts. Each function is independent and can be used for completely different tasks. The complete source code, with tests and documentation, can be found here: http://github.com/hradtke/crimson/tree/master/Crimson_Url
Reblog this post [with Zemanta]