SiteCrafting, Inc.

13 Dec
URL: U R a Lifesaver!

URL: U R a Lifesaver!

There's only one thing that sucks more than a Dyson Ball, and that's dealing with complex URLs that have parameter strings so long it feels like the URL equivalent of a run-on-sentence that doesn't seem to ever stop yet every piece has its own reason for being there. Do you feel my pain? Then read on to find eternal salvation....



What's a developer to do when there's no intuitive built-in method for dealing with these things? Enter the URL (a.k.a. MakeYourLifeEasier) class. Before we get into the nitty gritty, why don't we let it show off a bit with a few examples.

/**
 * Example 1: Inspect a sample URL.
 */

$url = new URL('http://leeroy:jenkins@www.sitecrafting.com:1337/path/to/subdir/example.php?id=3&page=2#some_fragment');

echo 
'Protocol: ' $url->protocol;        // Produces "Protocol: http"
echo 'Host: ' $url->host;                // Produces "Host: www.sitecrafting.com"
echo 'Port: ' $url->port;                // Produces "Port: 1337"
echo 'Username: ' $url->username;        // Produces "Username: leeroy"
echo 'Password: ' $url->password;        // Produces "Password: jenkins"
echo 'Path: ' $url->path;                // Produces "Path: /path/to/subdir/example.php"
echo 'Fragment: ' $url->fragment;        // Produces "Fragment: some_fragment"

foreach($url as $key => $value) {
    echo 
$key ' = ' $value;
}

// Produces:
//        "id = 3"
//        "page = 2"

/**
 * Example 2: Adding to the current page's URL.
 */

// An empty URL defaults to the currently executing script's URL.
// Let's assume the same URL from the previous example.
$url = new URL();

// Modify the page number.
$url['page'] = 3;

// Link to the next page.
echo '<a href="' $url '">Next Page</a>';

/**
 * Example 3: Copying an existing URL.
 */

$url = new URL();

// Calling URL::copy() makes a clone of the URL object and all its properties.
// Pass in an array of key/value pairs to simultaneously assign new values.

// Link to the previous page.
echo '<a href="' $url->copy(array('page' => 1)) . '">Previous Page</a>';

// Link to the next page.
echo '<a href="' $url->copy(array('page' => 3)) . '">Next Page</a>';

As you can see, this class is great for both inspecting and modifying URLs. The class itself takes advantage of some of PHP's advanced SPL features:

       
  • The various components of a URL are accessed as properties of the URL class:

    $url->protocol
  •    
  • Query string variables are accessed simply by treating the URL object as an array:

    $url['page']
  •    
  • You can even loop through it or call count() on it like a real array.
  •    
  • Simply use (or cast) the object as a string to get the assembled URL:

    echo 'URL: ' . $url

And now for the moment you've all been waiting for, the guts:

<?php
/**
 * URL Object
 *
 * Provides an object representation of a URL upon which operations can be made.
 *
 * @author SiteCrafting, Inc. (Nick Williams)
 * @version 1.0.0
 */
class URL implements ArrayAccessCountableSeekableIterator {
    protected 
$_protocol;
    protected 
$_host;
    protected 
$_port;
    protected 
$_username;
    protected 
$_password;
    protected 
$_path;
    protected 
$_fragment;
    protected 
$_params;

    
/**
     * Initializes a new URL object.
     *
     * @param mixed $urlString a URL string with which to initialize, or true to use the current request URL
     */
    
public function __construct($urlString null) {
        
// Default to Current URL
        
if($urlString === null) {
            
$urlString 'http' . ($_SERVER['HTTPS'] ? 's' '') . '://' $_SERVER['HTTP_HOST'] . ($_SERVER['SERVER_PORT'] != '80' ':' $_SERVER['SERVER_PORT'] : '') . $_SERVER['REQUEST_URI'];
        }

        
// Parse URL
        
$parts parse_url($urlString);

        if(
$parts) {
            
$this->_protocol $parts['scheme'];
            
$this->_host $parts['host'];
            
$this->_port $parts['port'];
            
$this->_username $parts['user'];
            
$this->_password $parts['pass'];
            
$this->_path $parts['path'];
            
$this->_fragment $parts['fragment'];
            
$this->_params = array();

            
$params explode('&'$parts['query']);

            foreach(
$params as $param) {
                list(
$key$value) = array_map('urldecode'explode('='$param));
                
$this->_params[$key] = $value;
            }
        }
    }

    
/**
     * Returns the current URL as a string.
     *
     * @return string the assembled URL
     */
    
public function __toString() {
        
// Build: Protocol
        
$result $this->_protocol '://';

        
// Build: Username/Password
        
if($this->_username) {
            
$result .= $this->_username;

            if(
$this->_password) {
                
$result .= ':' $this->_password;
            }

            
$result .= '@';
        }

        
// Build: Host
        
$result .= $this->_host;

        
// Build: Port
        
if($this->_port) {
            
$result .= ':' $this->_port;
        }

        
// Build: Path
        
if($this->_path) {
            
$result .= $this->_path;
        }

        
// Build: Parameters
        
if(is_array($this->_params) && count($this->_params)) {
            
$result .= '?' http_build_query($this->_params);
        }

        
// Build: Fragment
        
if($this->_fragment) {
            
$result .= '#' urlencode($this->_fragment);
        }

        return 
$result;
    }

    
/**
     * Sets the specified property. Valid properties include protocol, host,
     * port, username, password, path, and fragment.
     *
     * @param string $name the name of the property to be set
     * @param string $value the value to assign to the specified property
     */
    
public function __set($name$value) {
        switch(
$name) {
            default:
                throw new 
Exception('Invalid property "' $name '" specified.');
                break;

            case 
'protocol':
            case 
'host':
            case 
'port':
            case 
'username':
            case 
'password':
            case 
'path':
            case 
'fragment':
                
$this->_{$name} = $value;
                break;
        }
    }

    
/**
     * Retrieves the current value for the specified property. Valid properties
     * include protocol, host, port, username, password, path, and fragment.
     *
     * @return string the corresponding property's value
     */
    
public function __get($name) {
        switch(
$name) {
            default:
                throw new 
Exception('Invalid property "' $name '" specified.');
                break;

            case 
'protocol':
            case 
'host':
            case 
'port':
            case 
'username':
            case 
'password':
            case 
'path':
            case 
'fragment':
                return 
$this->_{$name};
                break;
        }
    }

    
/**
     * Checks if the specified URL parameter exists.
     *
     * @param string $offset the name of the parameter to be checked
     * @return boolean whether or not the parameter exists
     */
    
public function  offsetExists($offset) {
        return isset(
$this->_params[$offset]);
    }

    
/**
     * Retrieves the requested URL parameter.
     *
     * @param string $offset the name of the parameter to be retrieved
     * @return string the corresponding parameter's value
     */
    
public function  offsetGet($offset) {
        return isset(
$this->_params[$offset]) ? $this->_params[$offset] : null;
    }

    
/**
     * Sets the specified URL parameter to the specified value.
     *
     * @param string $offset the name of the parameter to be set
     * @param string $value the value to be assigned
     */
    
public function  offsetSet($offset$value) {
        
$this->_params[$offset] = $value;
    }

    
/**
     * Removes the specified URL parameter.
     *
     * @param string $offset the name of the parameter to be removed
     */
    
public function  offsetUnset($offset) {
        unset(
$this->_params[$offset]);
    }

    
/**
     * Returns the total number of URL parameters currently set.
     *
     * @return integer the calculated total
     */
    
public function count() {
        return 
count($this->_params);
    }

    
/**
     * Returns the value of the current parameter.
     *
     * @return string the corresponding value
     */
    
public function current() {
        return 
current($this->_params);
    }

    
/**
     * Returns the key for the current parameter.
     *
     * @return scalar the current key
     */
    
public function key() {
        return 
key($this->_params);
    }

    
/**
     * Advances the internal array pointer for the currently stored parameters.
     */
    
public function next() {
        return 
next($this->_params);
    }

    
/**
     * Rewinds the internal array pointer for the currently stored parameters.
     */
    
public function rewind() {
        return 
rewind($this->_params);
    }

    
/**
     * Checks if the current internal array pointer is pointing to a valid parameter.
     */
    
public function valid() {
        return (
current($this->_params) !== false);
    }

    
/**
     * Attempts to move the internal array pointer to the specified location.
     *
     * @param int $index the desired position
     */
    
public function  seek($index) {
        
$this->rewind();
        
$position 0;

        while(
$position $index && $this->valid()) {
            
$this->next();
            
$position++;
        }

        if(!
$this->valid()) {
            throw new 
Exception('Invalid seek position.');
        }
    }

    
/**
     * Returns a copy of the current URL object.
     *
     * @param array $params an optional array of parameters to add/replace within the copied URL
     * @return self the newly created copy
     */
    
public function copy($params = array()) {
        
// Setup
        
$result = new self($this->__toString());

        foreach(
$params as $key => $value) {
            
$result[$key] = $value;
        }

        return 
$result;
    }
}
?>

Hopefully this will find its way into your life, saving you some hours and possibly a few bottles of aspirin. If you've found this class useful or have any suggestions for improvements, feel free to share in the comments!

Coding Techniques, From the Workbench, PHP, Software Engineering
by Nick Williams | 12/13/2010 3:05pm | Comments (0)

No comments found.


Leave a Comment




* required    Comment Guidelines