Track route depth on the Request, not on the Router.
This allows a Handler to use the top-level router and have the depth count match the depth for the current request, not the total.
This commit is contained in:
parent
d91ac12541
commit
e3e98377c3
33
README.md
33
README.md
|
|
@ -32,7 +32,7 @@ $ curl -s https://getcomposer.org/installer | php
|
|||
$ php composer.phar install
|
||||
```
|
||||
|
||||
You can now use WellRESTed by including the **autoload.php** file generated by Composer. (vendor/autoload.php)
|
||||
You can now use WellRESTed by including the **autoload.php** file generated by Composer. `vendor/autoload.php`
|
||||
|
||||
|
||||
Examples
|
||||
|
|
@ -55,11 +55,11 @@ $response = $myRouter->getResponse();
|
|||
$response->respond();
|
||||
```
|
||||
|
||||
When you create your Handler subclass, you will provide a method for each HTTP verb you would like the endpoint to support. For example, if **/things/** should support GET, you would override the get() method. For POST, post(), etc.
|
||||
When you create your Handler subclass, you will provide a method for each HTTP verb you would like the endpoint to support. For example, if `/things/` should support `GET`, you would override the `get()` method. For `POST`, `post()`, etc.
|
||||
|
||||
If your endpoint should reject particular verbs, no worries. The Handler base class defines the default verb-handling methods to respond with a 405 Method Not Allowed status.
|
||||
If your endpoint should reject particular verbs, no worries. The Handler base class defines the default verb-handling methods to respond with a **405 Method Not Allowed** status.
|
||||
|
||||
Here's a simple Handler that matches the first endpoint, **/things/**.
|
||||
Here's a simple Handler that matches the first endpoint, `/things/`.
|
||||
|
||||
```php
|
||||
class ThingCollectionHandler extends \pjdietz\WellRESTed\Handler
|
||||
|
|
@ -88,7 +88,7 @@ class ThingCollectionHandler extends \pjdietz\WellRESTed\Handler
|
|||
}
|
||||
```
|
||||
|
||||
This Handler works with the second endpoint, **/things/{id}**. The pattern for this endpoint has the variable **{id}** in it. The Handler can access path variables through its **args** member, which is an associative array of variables from the URI.
|
||||
This Handler works with the second endpoint, `/things/{id}`. The pattern for this endpoint has the variable `{id}` in it. The Handler can access path variables through its `args` member, which is an associative array of variables from the URI.
|
||||
|
||||
```php
|
||||
class ThingItemHandler extends \pjdietz\WellRESTed\Handler
|
||||
|
|
@ -113,6 +113,23 @@ class ThingItemHandler extends \pjdietz\WellRESTed\Handler
|
|||
}
|
||||
```
|
||||
|
||||
As of version 1.3.0, you can have a Router dispatch other Routers. This is useful for breaking your endpoints into smaller route tables. Here's an example:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Top-level router that dispatches all traffic to /cat/... to CatRouter and all
|
||||
* traffic to /dog/... to DogRouter.
|
||||
*/
|
||||
class TopRouter extends \pjdietz\WellRESTed\Router
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->addRoute(new Route('/^\/cat\//', 'CatRouter'));
|
||||
$this->addRoute(new Route('/^\/dog\//', 'DogRouter'));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Requests and Responses
|
||||
|
||||
|
|
@ -130,6 +147,8 @@ exit;
|
|||
The Request class goes hand-in-hand with the Response class. Again, this is used in the Handler class to read the information from the request being handled. From outside the context of a Handler, you can also use the Request class to read info for the request sent to the server.
|
||||
|
||||
```php
|
||||
// Call the static method getRequest() to get a reference to the Request
|
||||
// singleton that represents the request made to the server.
|
||||
$rqst = \pjdietz\WellRESTed\Request::getRequest();
|
||||
|
||||
if ($rqst->getMethod() === 'PUT') {
|
||||
|
|
@ -161,7 +180,7 @@ if ($resp->getStatusCode() === 201) {
|
|||
|
||||
### Routing from a Handler
|
||||
|
||||
One more fun thing you can do with WellRESTed is use your API from inside your API. WellRESTed makes this easy because each dispatched Handler keeps a reference to the Router that dispatched it.
|
||||
One more fun thing you can do with WellRESTed is use your API from inside your API. WellRESTed makes this easy because each dispatched Handler or Router keeps a reference to the top-level Router.
|
||||
|
||||
Suppose you have an endpoint that needs to look up information using another endpoint. Now that you've seen that you can create and work with your own requests and responses, we'll look at how to use them in the context of a handler.
|
||||
|
||||
|
|
@ -197,4 +216,4 @@ For more examples, see the project [pjdietz/wellrested-samples](https://github.c
|
|||
Copyright and License
|
||||
---------------------
|
||||
Copyright © 2013 by PJ Dietz
|
||||
Licensed under the [MIT license](http://opensource.org/licenses/MIT)
|
||||
Licensed under the [MIT license](http://opensource.org/licenses/MIT)
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
namespace pjdietz\WellRESTed;
|
||||
|
||||
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RequestInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RoutableInterface;
|
||||
|
||||
/**
|
||||
* A Handler issues a response for a given resource.
|
||||
|
|
@ -22,10 +22,10 @@ use pjdietz\WellRESTed\Interfaces\ResponseInterface;
|
|||
abstract class Handler extends RouteTarget implements HandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param RoutableInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse(RequestInterface $request = null)
|
||||
public function getResponse(RoutableInterface $request = null)
|
||||
{
|
||||
if (!is_null($request)) {
|
||||
$this->request = $request;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace pjdietz\WellRESTed\Interfaces;
|
||||
|
||||
interface RoutableInterface
|
||||
{
|
||||
/** @return string HTTP request method (e.g., GET, POST, PUT). */
|
||||
public function getMethod();
|
||||
|
||||
/** @return string The path component of a URI for this Routeable */
|
||||
public function getPath();
|
||||
|
||||
/** @return int The number of times a router has dispatched this Routable */
|
||||
public function getRouteDepth();
|
||||
|
||||
/** Increase the instance's internal count of its depth in nested route tables */
|
||||
public function incrementRouteDepth();
|
||||
}
|
||||
|
|
@ -22,11 +22,11 @@ interface RouteTargetInterface
|
|||
/** @param array $args Associative array used to obtain a response */
|
||||
public function setArguments(array $args);
|
||||
|
||||
/** @return RequestInterface Request used to obtain a response */
|
||||
/** @return RoutableInterface Request used to obtain a response */
|
||||
public function getRequest();
|
||||
|
||||
/** @param RequestInterface $request Request used to obtain a response */
|
||||
public function setRequest(RequestInterface $request);
|
||||
/** @param RoutableInterface $request Request used to obtain a response */
|
||||
public function setRequest(RoutableInterface $request);
|
||||
|
||||
/** @return RouterInterface Reference to the router used to dispatch this handler */
|
||||
public function getRouter();
|
||||
|
|
@ -37,8 +37,8 @@ interface RouteTargetInterface
|
|||
/**
|
||||
* Return the response for the given request.
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param RoutableInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse(RequestInterface $request = null);
|
||||
public function getResponse(RoutableInterface $request = null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace pjdietz\WellRESTed;
|
|||
|
||||
use pjdietz\WellRESTed\Exceptions\CurlException;
|
||||
use pjdietz\WellRESTed\Interfaces\RequestInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RoutableInterface;
|
||||
|
||||
/**
|
||||
* A Request instance represents an HTTP request. This class has two main uses:
|
||||
|
|
@ -30,7 +31,7 @@ use pjdietz\WellRESTed\Interfaces\RequestInterface;
|
|||
* @property array query Associative array of query parameters
|
||||
* @property array uri Full URI (protocol, hostname, path, etc.)
|
||||
*/
|
||||
class Request extends Message implements RequestInterface
|
||||
class Request extends Message implements RequestInterface, RoutableInterface
|
||||
{
|
||||
/**
|
||||
* Singleton instance derived from reading info from Apache.
|
||||
|
|
@ -51,6 +52,8 @@ class Request extends Message implements RequestInterface
|
|||
private $port = 80;
|
||||
/**@var array Associative array of query parameters */
|
||||
private $query;
|
||||
/** @var int internal count of the number of times routers have dispatched this instance */
|
||||
private $routeDepth = 0;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -72,30 +75,6 @@ class Request extends Message implements RequestInterface
|
|||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Accessors
|
||||
|
||||
/**
|
||||
* Set the URI for the Request. This sets the other members: hostname,
|
||||
* path, port, and query.
|
||||
*
|
||||
* @param string $uri
|
||||
*/
|
||||
public function setUri($uri)
|
||||
{
|
||||
$parsed = parse_url($uri);
|
||||
|
||||
$host = isset($parsed['host']) ? $parsed['host'] : '';
|
||||
$this->setHostname($host);
|
||||
|
||||
$path = isset($parsed['path']) ? $parsed['path'] : '';
|
||||
$this->setPath($path);
|
||||
|
||||
$port = isset($parsed['port']) ? (int) $parsed['port'] : 80;
|
||||
$this->setPort($port);
|
||||
|
||||
$query = isset($parsed['query']) ? $parsed['query'] : '';
|
||||
$this->setQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a reference to the singleton instance of the Request derived
|
||||
|
|
@ -115,24 +94,6 @@ class Request extends Message implements RequestInterface
|
|||
return self::$theRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set instance members based on the HTTP request sent to the server.
|
||||
*/
|
||||
public function readHttpRequest()
|
||||
{
|
||||
$this->setBody(file_get_contents("php://input"), false);
|
||||
$this->headers = self::getRequestHeaders();
|
||||
|
||||
// Add case insensitive headers to the lookup table.
|
||||
foreach ($this->headers as $key => $value) {
|
||||
$this->headerLookup[strtolower($key)] = $key;
|
||||
}
|
||||
|
||||
$this->method = $_SERVER['REQUEST_METHOD'];
|
||||
$this->uri = $_SERVER['REQUEST_URI'];
|
||||
$this->hostname = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
|
||||
/** @return array all request headers from the current request. */
|
||||
public static function getRequestHeaders()
|
||||
{
|
||||
|
|
@ -165,6 +126,24 @@ class Request extends Message implements RequestInterface
|
|||
return $arh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set instance members based on the HTTP request sent to the server.
|
||||
*/
|
||||
public function readHttpRequest()
|
||||
{
|
||||
$this->setBody(file_get_contents("php://input"), false);
|
||||
$this->headers = self::getRequestHeaders();
|
||||
|
||||
// Add case insensitive headers to the lookup table.
|
||||
foreach ($this->headers as $key => $value) {
|
||||
$this->headerLookup[strtolower($key)] = $key;
|
||||
}
|
||||
|
||||
$this->method = $_SERVER['REQUEST_METHOD'];
|
||||
$this->uri = $_SERVER['REQUEST_URI'];
|
||||
$this->hostname = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hostname portion of the URI
|
||||
*
|
||||
|
|
@ -297,6 +276,67 @@ class Request extends Message implements RequestInterface
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full URI includeing protocol, hostname, path, and query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
$uri = strtolower($this->protocol) . '://' . $this->hostname;
|
||||
|
||||
if ($this->port !== 80) {
|
||||
$uri .= ':' . $this->port;
|
||||
}
|
||||
|
||||
$uri .= $this->path;
|
||||
|
||||
if ($this->query) {
|
||||
$uri .= '?' . http_build_query($this->query);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI for the Request. This sets the other members: hostname,
|
||||
* path, port, and query.
|
||||
*
|
||||
* @param string $uri
|
||||
*/
|
||||
public function setUri($uri)
|
||||
{
|
||||
$parsed = parse_url($uri);
|
||||
|
||||
$host = isset($parsed['host']) ? $parsed['host'] : '';
|
||||
$this->setHostname($host);
|
||||
|
||||
$path = isset($parsed['path']) ? $parsed['path'] : '';
|
||||
$this->setPath($path);
|
||||
|
||||
$port = isset($parsed['port']) ? (int) $parsed['port'] : 80;
|
||||
$this->setPort($port);
|
||||
|
||||
$query = isset($parsed['query']) ? $parsed['query'] : '';
|
||||
$this->setQuery($query);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** @return int The number of times a router has dispatched this Routable */
|
||||
public function getRouteDepth()
|
||||
{
|
||||
return $this->routeDepth;
|
||||
}
|
||||
|
||||
/** Increase the instance's internal count of its depth in nested route tables */
|
||||
public function incrementRouteDepth()
|
||||
{
|
||||
$this->routeDepth++;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Make a cURL request out of the instance and return a Response.
|
||||
*
|
||||
|
|
@ -382,28 +422,4 @@ class Request extends Message implements RequestInterface
|
|||
return $resp;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return the full URI includeing protocol, hostname, path, and query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
$uri = strtolower($this->protocol) . '://' . $this->hostname;
|
||||
|
||||
if ($this->port !== 80) {
|
||||
$uri .= ':' . $this->port;
|
||||
}
|
||||
|
||||
$uri .= $this->path;
|
||||
|
||||
if ($this->query) {
|
||||
$uri .= '?' . http_build_query($this->query);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
namespace pjdietz\WellRESTed;
|
||||
|
||||
use pjdietz\WellRESTed\Interfaces\RequestInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RoutableInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RouterInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RouteTargetInterface;
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ abstract class RouteTarget implements RouteTargetInterface
|
|||
{
|
||||
/** @var array Matches array from the preg_match() call used to find this Handler */
|
||||
protected $args;
|
||||
/** @var RequestInterface The HTTP request to respond to. */
|
||||
/** @var RoutableInterface The HTTP request to respond to. */
|
||||
protected $request;
|
||||
/** @var ResponseInterface The HTTP response to send based on the request. */
|
||||
protected $response;
|
||||
|
|
@ -44,14 +44,14 @@ abstract class RouteTarget implements RouteTargetInterface
|
|||
return $this->args;
|
||||
}
|
||||
|
||||
/** @return RequestInterface */
|
||||
/** @return RoutableInterface */
|
||||
public function getRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/** @param RequestInterface $request */
|
||||
public function setRequest(RequestInterface $request)
|
||||
/** @param RoutableInterface $request */
|
||||
public function setRequest(RoutableInterface $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
namespace pjdietz\WellRESTed;
|
||||
|
||||
use pjdietz\WellRESTed\Interfaces\RequestInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RoutableInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RouteInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RouterInterface;
|
||||
use pjdietz\WellRESTed\Interfaces\RouteTargetInterface;
|
||||
|
|
@ -31,8 +31,6 @@ class Router extends RouteTarget implements RouterInterface
|
|||
protected $maxDepth = self::MAX_DEPTH;
|
||||
/** @var array Array of Route objects */
|
||||
private $routes;
|
||||
/** @var int counter incrememted each time a router dispatches a route target. */
|
||||
private $depth = 0;
|
||||
|
||||
/** Create a new Router. */
|
||||
public function __construct()
|
||||
|
|
@ -50,13 +48,21 @@ class Router extends RouteTarget implements RouterInterface
|
|||
$this->routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int maximum levels of routing before the router raises an error.
|
||||
*/
|
||||
public function getMaxDepth()
|
||||
{
|
||||
return $this->maxDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the response built by the handler based on the request
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param RoutableInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse(RequestInterface $request = null)
|
||||
public function getResponse(RoutableInterface $request = null)
|
||||
{
|
||||
// Set the instance's request, if the called passed one.
|
||||
if (!is_null($request)) {
|
||||
|
|
@ -68,8 +74,13 @@ class Router extends RouteTarget implements RouterInterface
|
|||
}
|
||||
// Reference the request and path.
|
||||
$request = $this->request;
|
||||
$request->incrementRouteDepth();
|
||||
$path = $request->getPath();
|
||||
|
||||
if ($request->getRouteDepth() >= $this->getMaxDepth()) {
|
||||
return $this->getInternalServerErrorResponse($request, 'Maximum route recursion reached.');
|
||||
}
|
||||
|
||||
foreach ($this->routes as $route) {
|
||||
/** @var RouteInterface $route */
|
||||
if (preg_match($route->getPattern(), $path, $matches)) {
|
||||
|
|
@ -80,7 +91,7 @@ class Router extends RouteTarget implements RouterInterface
|
|||
$target = new $targetClassName();
|
||||
$target->setRequest($request);
|
||||
|
||||
// If this instance already had argument, merge the matches with them.
|
||||
// If this instance already had arguments, merge the matches with them.
|
||||
$myArguments = $this->getArguments();
|
||||
if (!is_null($myArguments)) {
|
||||
$matches = array_merge($myArguments, $matches);
|
||||
|
|
@ -90,10 +101,8 @@ class Router extends RouteTarget implements RouterInterface
|
|||
// If this instance already had a top-level router, pass it along.
|
||||
// Otherwise, pass itself as the top-level router.
|
||||
if (isset($this->router)) {
|
||||
$this->router->incrementDepth();
|
||||
$target->setRouter($this->router);
|
||||
} else {
|
||||
$this->incrementDepth();
|
||||
$target->setRouter($this);
|
||||
}
|
||||
|
||||
|
|
@ -108,34 +117,27 @@ class Router extends RouteTarget implements RouterInterface
|
|||
return $this->getNoRouteResponse($request);
|
||||
}
|
||||
|
||||
public function incrementDepth()
|
||||
{
|
||||
$this->depth++;
|
||||
if ($this->depth >= $this->maxDepth) {
|
||||
$response = $this->getInternalServerErrorResponse(
|
||||
$this->getRequest(),
|
||||
'Maximum recursion level reached.'
|
||||
);
|
||||
$response->respond();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a resonse indicating a 404 Not Found error
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param RoutableInterface $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function getNoRouteResponse(RequestInterface $request)
|
||||
protected function getNoRouteResponse(RoutableInterface $request)
|
||||
{
|
||||
$response = new Response(404);
|
||||
$response->body = 'No resource at ' . $request->getPath();
|
||||
return $response;
|
||||
}
|
||||
|
||||
/** Prepare a response indicating a 500 Internal Server Error */
|
||||
protected function getInternalServerErrorResponse(RequestInterface $request, $message = '')
|
||||
/**
|
||||
* Prepare a response indicating a 500 Internal Server Error
|
||||
*
|
||||
* @param RoutableInterface $request
|
||||
* @param string $message Optional additional message.
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
protected function getInternalServerErrorResponse(RoutableInterface $request, $message = '')
|
||||
{
|
||||
$response = new Response(500);
|
||||
$response->setBody('Server error at ' . $request->getPath() . "\n" . $message);
|
||||
|
|
|
|||
Loading…
Reference in New Issue