Move some logic from Router to Route for easier subclassing and better OOP principles. Add comments and PHPDoc blocks.

This commit is contained in:
PJ Dietz 2012-09-16 22:31:25 -04:00
parent 37b75d69d1
commit 5af80d8b1d
5 changed files with 118 additions and 62 deletions

View File

@ -8,6 +8,8 @@ require_once(dirname(__FILE__) . '/Response.inc.php');
/******************************************************************************* /*******************************************************************************
* Handler * Handler
* *
* A Handler issues a response for a given resource.
*
* @package WellRESTed * @package WellRESTed
* *
******************************************************************************/ ******************************************************************************/
@ -33,22 +35,22 @@ class Handler {
* Matches array from the preg_match() call used to find this Handler. * Matches array from the preg_match() call used to find this Handler.
* @var array * @var array
*/ */
protected $matches; protected $args;
/** /**
* Create a new Handler for a specific request. * Create a new Handler for a specific request.
* *
* @param Request $request * @param Request $request
* @param array $matches * @param array $args
*/ */
public function __construct($request, $matches=null) { public function __construct($request, $args=null) {
$this->request = $request; $this->request = $request;
if (is_null($matches)) { if (is_null($args)) {
$matches = array(); $args = array();
} }
$this->matches = $matches; $this->args = $args;
$this->response = new Response(); $this->response = new Response();
$this->buildResponse(); $this->buildResponse();
@ -64,11 +66,14 @@ class Handler {
case 'response': case 'response':
return $this->getResponse(); return $this->getResponse();
default: default:
throw new Exception('Property ' . $name . ' does not exist.'); throw new \Exception('Property ' . $name . ' does not exist.');
} }
} // __get() } // __get()
/**
* @return Response
*/
public function getResponse() { public function getResponse() {
return $this->response; return $this->response;
} }
@ -160,6 +165,13 @@ class Handler {
$this->response->statusCode = 405; $this->response->statusCode = 405;
} }
/**
* Method for handling HTTP DELETE requests.
*/
protected function delete() {
$this->response->statusCode = 405;
}
/** /**
* Method for handling HTTP PATCH requests. * Method for handling HTTP PATCH requests.
*/ */

View File

@ -89,7 +89,7 @@ namespace wellrested;
case 'query': case 'query':
return $this->getQuery(); return $this->getQuery();
default: default:
throw new Exception('Property ' . $name . ' does not exist.'); throw new \Exception('Property ' . $name . ' does not exist.');
} }
} // __get() } // __get()
@ -118,13 +118,12 @@ namespace wellrested;
return $this->query; return $this->query;
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
/** /**
* Set instance members based on the HTTP request sent to the server. * Set instance members based on the HTTP request sent to the server.
*/ */
public function readHttpRequest() { protected function readHttpRequest() {
$this->body = file_get_contents("php://input"); $this->body = file_get_contents("php://input");

View File

@ -62,7 +62,7 @@ class Response {
case 'headers': case 'headers':
return $this->getHeaders(); return $this->getHeaders();
default: default:
throw new Exception('Property ' . $name . ' does not exist.'); throw new \Exception('Property ' . $name . ' does not exist.');
} }
} // __get() } // __get()
@ -71,9 +71,10 @@ class Response {
switch ($name) { switch ($name) {
case 'body': case 'body':
return $this->setBody($value); $this->setBody($value);
break;
default: default:
throw new Exception('Property ' . $name . ' does not exist or is read only.'); throw new \Exception('Property ' . $name . ' does not exist or is read only.');
} }
} // __set() } // __set()
@ -92,7 +93,7 @@ class Response {
* of the new body string. * of the new body string.
* *
* @param string $value * @param string $value
* @param bool $setContentLenght Automatically add a Content-length header * @param bool $setContentLength Automatically add a Content-length header
*/ */
public function setBody($value, $setContentLength=true) { public function setBody($value, $setContentLength=true) {
@ -118,7 +119,7 @@ class Response {
* Return if the response contains a header with the given key. * Return if the response contains a header with the given key.
* *
* @param string $header * @param string $header
* @param bool * @return bool
*/ */
public function hasHeader($header) { public function hasHeader($header) {
return isset($this->headers[$header]); return isset($this->headers[$header]);
@ -128,7 +129,7 @@ class Response {
* Return the value of a given header, or false if it does not exist. * Return the value of a given header, or false if it does not exist.
* *
* @param string $header * @param string $header
* @return string|false * @return string|bool
*/ */
public function getHeader($header) { public function getHeader($header) {
@ -164,6 +165,7 @@ class Response {
public function respond($headersOnly = false) { public function respond($headersOnly = false) {
// Output the HTTP status code. // Output the HTTP status code.
// TODO: Available in 5.4+. PHP.net has a good alternative in the comments.
http_response_code($this->statusCode); http_response_code($this->statusCode);
// Output each header. // Output each header.

View File

@ -16,14 +16,38 @@ class Route {
const RE_ALPHA = '[a-zA-Z]+'; const RE_ALPHA = '[a-zA-Z]+';
const RE_ALPHANUM = '[0-9a-zA-Z]+'; const RE_ALPHANUM = '[0-9a-zA-Z]+';
const URI_TEMPLATE_EXPRESSION_RE = '/{([a-zA-Z]+)}/';
/**
* Regular Expression to use to validate a template variable.
* @var string
*/
static public $defaultVariablePattern = self::RE_SLUG; static public $defaultVariablePattern = self::RE_SLUG;
/**
* Regular expression used to match a Request URI path component
* @var string
*/
public $pattern; public $pattern;
public $handler;
public $handlerPath;
public $uriTemplate;
public function __construct($pattern, $handler, $handlerPath=null) { /**
* Name of the Handler class to use
* @var string
*/
public $handler;
/**
* The path to the source file defing the handler class.
* @var string
*/
public $handlerPath;
/**
* @param $pattern
* @param $handler
* @param $handlerPath
*/
public function __construct($pattern, $handler, $handlerPath) {
$this->pattern = $pattern; $this->pattern = $pattern;
$this->handler = $handler; $this->handler = $handler;
@ -31,42 +55,67 @@ class Route {
} // __construct } // __construct
static public function newFromUriTemplate($uriTemplate, $handler, $handlerPath=null, $variables=null) { /**
* Create a new Route using a URI template to generate the pattern.
*
* @param string $uriTemplate
* @param string $handler
* @param string $handlerPath
* @param array $variables
* @throws \Exception
* @return Route
*/
static public function newFromUriTemplate($uriTemplate, $handler,
$handlerPath=null,
$variables=null) {
$pattern = ''; $pattern = '';
// REGEX for idenifying Level 1 URI Templates, like /foo/{bar}
$expressionPattern = '/{([a-zA-Z]+)}/';
// Explode the template into an array of path segments.
if ($uriTemplate[0] === '/') { if ($uriTemplate[0] === '/') {
$parts = explode('/', substr($uriTemplate, 1)); $parts = explode('/', substr($uriTemplate, 1));
} else { } else {
$parts = explode('/', $uriTemplate); $parts = explode('/', $uriTemplate);
} }
$expressionPattern = '/{([a-zA-Z]+)}/';
foreach ($parts as $part) { foreach ($parts as $part) {
$pattern .= '\/'; $pattern .= '\/';
if (preg_match($expressionPattern, $part, $matches)) { // Is this part an expression or a literal?
if (preg_match(self::URI_TEMPLATE_EXPRESSION_RE,
$part, $matches)) {
$variablePattern = self::$defaultVariablePattern; // This part of the path is an expresion.
if (count($matches) === 2) { if (count($matches) === 2) {
// Locate the name for the variable from the template.
$variableName = $matches[1]; $variableName = $matches[1];
if (isset($groups[$variableName])) { // If the caller passed an array with this variable name
$variablePattern = $groups[$variableName]; // as a key, use its value for the pattern here.
// Otherwise, use the class's current default.
if (isset($variables[$variableName])) {
$variablePattern = $variables[$variableName];
} else {
$variablePattern = self::$defaultVariablePattern;
} }
$pattern .= sprintf('(?<%s>%s)', $variableName,
$variablePattern);
} else {
// Not sure why this would happen.
throw new \Exception('Invalid URI Template.');
} }
$pattern .= sprintf('(?<%s>%s)', $variableName, $variablePattern);
} else { } else {
// This part is a literal.
$pattern .= $part; $pattern .= $part;
} }
} }
@ -75,7 +124,6 @@ class Route {
$klass = __CLASS__; $klass = __CLASS__;
$route = new $klass($pattern, $handler, $handlerPath); $route = new $klass($pattern, $handler, $handlerPath);
$route->uriTemplate = $uriTemplate;
return $route; return $route;
} // newFromUriTemplate() } // newFromUriTemplate()

View File

@ -8,6 +8,9 @@ require_once(dirname(__FILE__) . '/Route.inc.php');
/******************************************************************************* /*******************************************************************************
* Router * Router
* *
* A Router uses a table of Routes to find the appropriate Handler for a
* request.
*
* @package WellRESTed * @package WellRESTed
* *
******************************************************************************/ ******************************************************************************/
@ -16,53 +19,45 @@ class Router {
protected $routes; protected $routes;
public $handlerPathPattern = '%s.inc.php'; /**
* Create a new Router.
*/
public function __construct() { public function __construct() {
$this->routes = array(); $this->routes = array();
} }
protected function getHandlerPath($handler) { /**
return sprintf($this->handlerPathPattern, $handler); * Append a new Route instance to the Router's route table.
} * @param $route
*/
public function addRoute($pattern, $handler, $handlerPath=null) { public function addRoute(Route $route) {
$this->routes[] = $route;
if (is_null($handlerPath)) {
$handlerPath = $this->getHandlerPath($handler);
}
$this->routes[] = new Route($pattern, $handler, $handlerPath);
} // addRoute() } // addRoute()
public function addUriTemplate($uriTemplate, $handler, $handlerPath=null, $variables=null) { /**
* @param string $requestPath
* @return Handler
*/
public function getRequestHandler($requestPath=null) {
if (is_null($handlerPath)) { if (is_null($requestPath)) {
$handlerPath = $this->getHandlerPath($handler);
}
$this->routes[] = Route::newFromUriTemplate($uriTemplate, $handler, $handlerPath, $variables);
} // addUriTemplate()
public function getRequestHandler($request=null) {
if (is_null($request)) {
$request = Request::getRequest(); $request = Request::getRequest();
$path = $request->path;
} else {
$path = $requestPath;
} }
$path = $request->path;
foreach ($this->routes as $route) { foreach ($this->routes as $route) {
if (preg_match($route->pattern, $path, $matches)) { if (preg_match($route->pattern, $path, $matches)) {
if (!class_exists($route->handler)) { $klass = $route->handler;
if (!class_exists($klass)) {
require_once($route->handlerPath); require_once($route->handlerPath);
} }
return $handler = new $route->handler($request, $matches); return $handler = new $klass($request, $matches);
} }