PHP Class: ObjectOrientedURLs

Posted on Sep 20, 2009 @ 6:00 PM

ObjectOrientedURLs - A PHP class / frontend controller for calling methods from URLs. Licensed jointly under the GPL and MIT licenses.

ObjectOrientedURLs lets you call class methods directly from URLs, without having to upload a PHP file that instantiates your class and calls the method. All you need to to is write method names that match up to the URLs on your website.

For an added layer of security, a keyword must be prepended to all methods called by a URL (defaults to "render"). Your URL callable methods must be named with the keyword prepended to the method name. For example, the url /foo/bar/ would call a method named renderFooBar. This is to prevent people from accessing all of your public methods via URLs.

You can pass arguments to your methods in the URL by delimiting the with the default apache directory delimiter forward slash (/). For example, if you have a method named renderFooBar, you can pass it arguments by adding on to the URL as follows: /foo/bar/argument0/argument1/ After a method name match is found in the URL, the arguments are passed via an array to the method.

 

EXAMPLES:

  • /foo/bar/1 would call the method renderFooBar($args). $args[0] would be equal to 1
  • /foo/bar/1/2 would call the method renderFooBar($args). $args[0] would be equal to 1, $args[1] would be equal to 2
  • /foo/bar/index.php?id=1 would call the method renderFooBar($args). $args[0] would be equal to index.php?id=1 and $_GET["id"] would be equal to 1

ObjectOrientedURLs will only call methods when a file is not found. If the file is present on the server, it will be served by the webserver as normal.

 

CAVEATS:

  • You can call the same method with different URLs. /foo/bar, /foobar, and /f/o/o/b/a/r will all call the method renderFooBar($args). The default apache directory delimiter / is ignored until a method name has been matched. Then, it is not ignored and used to delimit any arguments.
  • Longer method names take precedence over shorter ones.
  • You cannot pass your base index method (/ if implemented at web root) any arguments.
  • Do not password protect pages by creating a real directory and uploading an .htaccess file. Your password protected directory can be circumvented by changing the URL (/protected/method/ changed to /protect/ed/method/ will call the same method). Implement user authentication within your class.
  • Only public methods may be called via URLs, private methods are unavailable.

 

REQUIREMENTS:

  • ObjectOrientedURLs uses the reflection API, which was introduced in PHP 5. While it has not been tested with older versions of PHP, ObjectOrientedURLs should work with PHP 5.0 or greater.
  • You need to be able to rewrite all 404 file not found requests to a PHP file that includes the ObjectOrientedURLs class.

 

DOWNLOAD:

Click here to download a working example, complete with the required htaccess file. To install, read the README.txt file.

<?php
class ObjectOrientedURLs
{
	//	you can change this to your preferred nomenclature.
	const PREPEND_TO_URL_METHODS = "render";
	
	//	you will need to change this if you choose to implement within a limited directory.
	const CONSTRAINED_TO_DIRECTORY = "";
	
	public function __construct()
	{
		$this->parseAndInvokeURL();
	}
	
	private function handleMethodNotFound($args)
	{
		header("HTTP/1.0 404 Not Found");
		error_log("method not found ".$_SERVER["REQUEST_URI"]);
		echo "method not found for ".$_SERVER["REQUEST_URI"]."<hr />";
		echo "this should be your custom 404 page not found handler<hr />";
		print_r($args);
	}
	
	private function parseAndInvokeURL()
	{
		$args = array();
		$directories = explode("/",$_SERVER["REQUEST_URI"]);
		$directories[0] = self::PREPEND_TO_URL_METHODS;
		while(!empty($directories))
		{
			if(end($directories) != "")
			{
				try
				{
					$Method = new ReflectionMethod($this,implode("",$directories));
					if($Method->isPublic() == true)
					{
						if($this->isIndexPage($Method->getName()) == true AND $this->isOnlyGetVarsOrEmpty($args) == false)
						{
							$this->handleMethodNotFound(array_reverse($args));
							return(true);
						}
						else
						{
							$Method->invoke($this,array_reverse($args));
							return(true);
						}
					}
				}
				catch(Exception $e)
				{
					//	this is thrown when a method doesn't exist.
				}
			}
			$args[] = end($directories);
			array_pop($directories);
		}
		$this->handleMethodNotFound($args);
		return(true);
	}
	
	private function isIndexPage($method)
	{
		if(strcasecmp($method,self::PREPEND_TO_URL_METHODS.self::CONSTRAINED_TO_DIRECTORY) == 0)
		{
			return(true);
		}
		else
		{
			return(false);
		}
	}
	
	private function isOnlyGetVarsOrEmpty($args)
	{
		if(count($args) == 1 AND (substr($args[0],0,1) == "?" OR $args[0] == ""))
		{
			return(true);
		}
		else
		{
			return(false);
		}
	}
}
?>

2 Comments

Click For New Security Code Please enter the security code: