From e77c85f71b4c32154e050e29e0652c83dd00b861 Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Sat, 26 Jul 2014 22:39:26 -0400 Subject: [PATCH] Update documentation and README --- README.md | 54 +++--------- documentation/handlers.md | 135 +++++++++++++++++++++++++++++ documentation/routes.md | 30 +++---- src/pjdietz/WellRESTed/Handler.php | 2 +- 4 files changed, 161 insertions(+), 60 deletions(-) create mode 100644 documentation/handlers.md diff --git a/README.md b/README.md index 3ad6f83..04f79b8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Requirements ------------ - PHP 5.3 -- [PHP cURL](http://php.net/manual/en/book.curl.php) for making requests with the `Client` class (Optional) +- [PHP cURL](http://php.net/manual/en/book.curl.php) for making requests with the [`Client`](src/pjdietz/WellRESTed/Client.php) class (Optional) Install @@ -40,7 +40,7 @@ Examples ### Routing -WellRESTed's primary goal is to facilitate mapping of URIs to classes that will provide or accept representations. To do this, create a `Router` instance and load it up with some `Route`s. Each `Route` is simply a mapping of a URI pattern to a class name. The class name represents the "handler" (any class implementing `HandlerInterface`) which the router will dispatch when it receives a request for the given URI. **The handlers are never instantiated or loaded unless they are needed.** +WellRESTed's primary goal is to facilitate mapping of URIs to classes that will provide or accept representations. To do this, create a [`Router`](src/pjdietz/WellRESTed/Router.php) instance and load it up with some routes. Each route is simply a mapping of a URI pattern to a class name. The class name represents the "handler" (any class implementing `HandlerInterface`) which the router will dispatch when it receives a request for the given URI. **The handlers are never instantiated or loaded unless they are needed.** ```php // Build the router. @@ -58,7 +58,7 @@ See [Routes](documentation/routes.md) to learn about the various route classes. ### Building Routes with JSON -WellRESTed also provides a class to construct routes for you based on a JSON description. Here's an example. +WellRESTed also provides a class to construct routes for you based on a JSON description. Here's an example: ```php $json = <<<'JSON' @@ -89,15 +89,13 @@ $router->addRoutes($routes); $router->respond(); ``` -Notice that when you build routes through JSON, you can provide a `handlerNamespace` to be affixed to the front of every `handler`. +Notice that when you build routes through JSON, you can provide a `handlerNamespace` to be affixed to the front of every handler. ### Handlers -Any class that implements `HandlerInterface` may be the handler for a route. This could be a class that builds the actual response, or it could another `Router`. +Any class that implements [`HandlerInterface`](src/pjdietz/WellRESTed/Interface/HandlerInterface.php) may be the handler for a route. This could be a class that builds the actual response, or it could another [`Router`](src/pjdietz/WellRESTed/Router.php). -For most cases, you'll want to use a subclass of the `Handler` class, which provides methods for responding based on HTTP method. When you create your Handler subclass, you will implement a method for each HTTP verb you would like the endpoint to support. For example, if `/cats/` 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. +For most cases, you'll want to use a subclass of the [`Handler`](src/pjdietz/WellRESTed/Handler.php) class, which provides methods for responding based on HTTP method. When you create your [`Handler`](src/pjdietz/WellRESTed/Handler.php) subclass, you will implement a method for each HTTP verb you would like the endpoint to support. For example, if `/cats/` should support `GET`, you would override the `get()` method. For `POST`, `post()`, etc. Here's a simple Handler that allows `GET` and `POST`. @@ -129,43 +127,11 @@ class CatsCollectionHandler extends \pjdietz\WellRESTed\Handler } ``` -#### Path Variables - -When you use a `TemplateRoute` with variables (or a `RegexRoute` with capture groups), you can access the variables (or captures) through the `Handler` member variable `$args`. - -Create this route... -```php -$route = TemplateRoute("/cats/{id}", "CatItemHandler"); -``` - -...which dispatches a `CatItemHandler` instance. -```php -class CatItemHandler extends \pjdietz\WellRESTed\Handler -{ - protected function get() - { - // Find a cat ($cat) based on $this->args["id"] - $id = $this->args["id"] - // ...do lookup here... - - if ($cat) { - // The cat exists! Let's output a representation. - $this->response->setStatusCode(200); - $this->response->setHeader("Content-Type", "application/json"); - $this->response->setBody(json_encode($cat)); - } else { - // The ID did not match anything. - $this->response->setStatusCode(404); - $this->response->setHeader("Content-Type", "text/plain"); - $this->response->setBody("No cat with id " . $this->args["id"]); - } - } -} -``` +See [Handlers](documentation/handler.md) to learn about the various route classes. ### Responses -You've already seen a `Response` in use in the examples above. You can also a `Response` outside of `Handler`. Let's take a look at creating a new `Response`, setting a header, supplying the body, and outputting. +You've already seen a [`Response`](src/pjdietz/WellRESTed/Response.php) in use in the examples above. You can also a [`Response`](src/pjdietz/WellRESTed/Response.php) outside of [`Handler`](src/pjdietz/WellRESTed/Handler.php). Let's take a look at creating a new [`Response`](src/pjdietz/WellRESTed/Response.php), setting a header, supplying the body, and outputting. ```php $resp = new \pjdietz\WellRESTed\Response(); @@ -178,7 +144,7 @@ exit; ### Requests -From outside the context of a `Handler`, you can also use the `Request` class to read info for the request sent to the server by using the static method `Request::getRequest()`. +From outside the context of a [`Handler`](src/pjdietz/WellRESTed/Handler.php), you can also use the [`Request`](src/pjdietz/WellRESTed/Request.php) class to read info for the request sent to the server by using the static method `Request::getRequest()`. ```php // Call the static method Request::getRequest() to get a reference to the Request @@ -194,7 +160,7 @@ if ($rqst->getMethod() === 'PUT') { ### HTTP Client -The `Client` class allows you to make an HTTP request using cURL. +The [`Client`](src/pjdietz/WellRESTed/Client.php) class allows you to make an HTTP request using cURL. (This feature requires [PHP cURL](http://php.net/manual/en/book.curl.php).) diff --git a/documentation/handlers.md b/documentation/handlers.md new file mode 100644 index 0000000..83bc853 --- /dev/null +++ b/documentation/handlers.md @@ -0,0 +1,135 @@ +# Handlers + + +[`Handler`](../src/pjdietz/WellRESTed/Handler.php) is an abstract base class for you to subclass to create controllers for generating responses given requests. + +## Instance Members + +Your [`Handler`](../src/pjdietz/WellRESTed/Handler.php) subclass has access to three protected members: + +Member | Type | Description +---------- | ---- | ----------- +`args` | `array` | Map of variables to supplement the request, usually path variables. +`request` | [`RequestInterface`](../src/pjdietz/WellRESTed/Interfaces/RequestInterface.php) | The HTTP request to respond to. +`response` | [`RequestInterface`](../src/pjdietz/WellRESTed/Interfaces/ResponseInterface.php) | The HTTP response to send based on the request. + + +## HTTP Verbs + +Most of the action takes place inside the methods called in response to specific HTTP verbs. For example, to handle a `GET` request, implement the `get` method. + +```php +class CatsCollectionHandler extends \pjdietz\WellRESTed\Handler +{ + protected function get() + { + // Read some cats from the database, cache, whatever. + // ...read these an array as the variable $cats. + + // Set the values for the instance's response member. This is what the + // Router will eventually output to the client. + $this->response->setStatusCode(200); + $this->response->setHeader("Content-Type", "application/json"); + $this->response->setBody(json_encode($cats)); + } +} +``` + +Implement the methods that you want to support. If you don't want to support `POST`, don't implement it. The default behavior is to respond with `405 Method Not Allowed` for most verbs. + +The methods available to implement are: + +HTTP Verb | Method | Default behavior +--------- | --------- | ---------------------- +`GET` | `get` | 405 Method Not Allowed +`HEAD` | `head` | Call `get`, then clean the response body +`POST` | `post` | 405 Method Not Allowed +`PUT` | `put` | 405 Method Not Allowed +`DELETE` | `delete` | 405 Method Not Allowed +`PATCH` | `patch` | 405 Method Not Allowed +`OPTIONS` | `options` | Add `Allow` header, if able + +### `OPTIONS` requests and `Allowed` headers + +An `OPTIONS` request to your endpoint should result in the API responding with an `Allow` header listing the verbs the endpoint supports. For example: + +``` +HTTP/1.1 200 OK +Allow: GET, HEAD, POST, OPTIONS +Content-Length: 0 +``` + +To support `OPTIONS` requests, implement `getAllowedMethods` and return an array of the methods you support. For a handler that supports the methods in the example response, do this: + +```php +protected function getAllowedMethods() +{ + return array("GET", "HEAD", "POST", "OPTIONS"); +} +``` + +You do not need to implement `options`. `options` by default calls `getAllowedMethods`. If it gets a return value, it sets the status code to `200 OK` and adds the `Allow` header. Otherwise, it responds `405 Method Not Allowed`. + +### Custom Verbs + +To support custom verbs, redefine the `buildResponse`. To respond to the custom verb `SNIFF`, to this: + +```php +protected function buildResponse() +{ + switch ($this->request->getMethod()) { + case 'SNIFF': + // Assuming you also made a sniff() method... + $this->sniff(); + break; + default: + // Let the parent's method do the rest. + self::buildResponse(); + } +} +``` + +## HttpExceptions + +Another useful feature of the [`Handler`](../src/pjdietz/WellRESTed/Handler.php) class is that it catches exceptions deriving from [`HttpException`](../src/pjdietz/WellRESTed/Exceptions/HttpExceptions) and turns them into responses. + +[`HttpException`](../src/pjdietz/WellRESTed/Exceptions/HttpExceptions) and its subclasses provide the status code and description for simple error responses. + +For example, you can throw a `NotFountException` if the resource the request indicates does not exist. + + +```php +use \pjdietz\WellRESTed\Handler; +use \pjdietz\WellRESTed\Exceptions\HttpExceptions\NotFoundException; + +class CatsCollectionHandler extends Handler +{ + protected function get() + { + // Lookup a cat by ID. + $cat = Cat::getById($this->args["id"]); + if (!$cat) { + throw new NotFoundException(); + } + + $this->response->setStatusCode(200); + $this->response->setHeader("Content-Type", "application/json"); + $this->response->setBody(json_encode($cat)); + } +} +``` + +Your [`Handler`](../src/pjdietz/WellRESTed/Handler.php) will automatically turn this into a `404 Not Found` response. + +Here are the available `HttpException` classes: + +Response Code | Class +--------------------------- | ----------------------- +`400 Bad Request` | `BadRequestException` +`401 Unauthorized` | `UnauthorizedException` +`403 Forbidden` | `ForbiddenException` +`404 Not Found` | `NotFoundException` +`409 Conflict` | `ConflictException` +`500 Internal Server Error` | `HttpException` + +You can also create your own by subclass `HttpException` and setting the exception's `$code` to the status code and `$messge` to a default message. diff --git a/documentation/routes.md b/documentation/routes.md index dc2090d..4454661 100644 --- a/documentation/routes.md +++ b/documentation/routes.md @@ -1,22 +1,22 @@ # Routes -WellRESTed comes with a few Route classes: +WellRESTed comes with a few route classes: -- `StaticRoute`: Matches request paths exactly -- `TemplateRoute`: Matches URI templates -- `RegexRoute`: Matches a custom regular expression +- [`StaticRoute`](../src/pjdietz/WellRESTed/Routes/StaticRoute.php): Matches request paths exactly +- [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php): Matches URI templates +- [`RegexRoute`](../src/pjdietz/WellRESTed/Routes/RegexRoute.php): Matches a custom regular expression -Each works basically the same way: It first checks to see if it is a match for the request. If it's a match, it instantiates a specific class implementing the `HandlerInterface` (autoloading the class, if needed). Finally, it uses the handler class to provide a response. +Each works basically the same way: It first checks to see if it is a match for the request. If it's a match, it instantiates a specific class implementing the [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php) (autoloading the class, if needed). Finally, it uses the handler class to provide a response. ## StaticRoute -Use a `StaticRoute` when you know the exact path you want to handle. This route will match only requests to `/cats/`. +Use a [`StaticRoute`](../src/pjdietz/WellRESTed/Routes/StaticRoute.php) when you know the exact path you want to handle. This route will match only requests to `/cats/`. ```php $route = new StaticRoute("/cats/", "CatHandler"); ``` -You can also make a `StaticRoute` that matches multiple exact paths. For example, suppose you have a multi-use `AnimalHandler` that you want to invoke to handle requests to `/cats/`, `/dogs/`, and `/birds/`. You can make this by passing an array instead of a string as the first parameter. +You can also make a [`StaticRoute`](../src/pjdietz/WellRESTed/Routes/StaticRoute.php) that matches multiple exact paths. For example, suppose you have a multi-use `AnimalHandler` that you want to invoke to handle requests to `/cats/`, `/dogs/`, and `/birds/`. You can make this by passing an array instead of a string as the first parameter. ```php $route = new StaticRoute(array("/cats/", "/dogs/", "/birds/"), "AnimalHandler"); @@ -24,7 +24,7 @@ $route = new StaticRoute(array("/cats/", "/dogs/", "/birds/"), "AnimalHandler"); ## TemplateRoute -`StaticRoutes` are the best choice if you know the exact path up front. But, what if you want to handle a path that includes a variable? That's where the `TemplateRoute` comes in. +[`StaticRoute`](../src/pjdietz/WellRESTed/Routes/StaticRoute.php) is the best choice if you know the exact path up front. But, what if you want to handle a path that includes a variable? That's where the [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) comes in. Here's a route that will match a request to a specific cat by ID and send it to a `CatItemHandler`. @@ -32,7 +32,7 @@ Here's a route that will match a request to a specific cat by ID and send it to $route = new TemplateRoute("/cats/{id}", "CatItemHandler"); ``` -A `TemplateRoute` use a URI template to match a request. To include a variable in your template, enclose it in `{}`. The variable will be extracted and made available for the handler in the handler's `args` member. +A [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) use a URI template to match a request. To include a variable in your template, enclose it in `{}`. The variable will be extracted and made available for the handler in the handler's `args` member. ```php class CatItemHandlder extends \pjdietz\WellRESTed\Handler @@ -48,7 +48,7 @@ class CatItemHandlder extends \pjdietz\WellRESTed\Handler Your template may have multiple variables. Be sure to give each a unique name. -With this `TemplateRoute`... +With this [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php)... ```php $route = new TemplateRoute("/cats/{catId}/{dogId}", "CatItemHandler"); @@ -59,13 +59,13 @@ $route = new TemplateRoute("/cats/{catId}/{dogId}", "CatItemHandler"); ### Default Variable Pattern -By default, the `TemplateRoute` will accept for a variable any value consisting of numbers, letters, underscores, and hyphens. You can change this behavior by passing a pattern to use as the third parameter of the constructor. Here we'll restrict the template to match only numeric values. +By default, the [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) will accept for a variable any value consisting of numbers, letters, underscores, and hyphens. You can change this behavior by passing a pattern to use as the third parameter of the constructor. Here we'll restrict the template to match only numeric values. ```php $route = new TemplateRoute("/cats/{id}", "CatItemHandler", TemplateRoute::RE_NUM); ``` -The `TemplateRoute` includes constants for some common situations. The value of each constant is a partial regular expression. You can use one of the constants, or provide your own partial regular expression. +The [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) includes constants for some common situations. The value of each constant is a partial regular expression. You can use one of the constants, or provide your own partial regular expression. ### Pattern Constants @@ -78,7 +78,7 @@ The `TemplateRoute` includes constants for some common situations. The value of ### Variable Patterns Array -You can also set a different pattern for each variable. To do this, pass an array to the `TemplateRoute` constructor as the fourth paramter. The array must have variable names as keys and patterns as values. +You can also set a different pattern for each variable. To do this, pass an array to the [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) constructor as the fourth parameter. The array must have variable names as keys and patterns as values. ```php $patterns = array( @@ -96,13 +96,13 @@ Here, `{id}` will need to match digits and `{name}` must be all letters. Since ` ### RegexRoute -If `TemplateRoute` doesn't give you enough control, you can make a route that matches a regular expression. +If [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) doesn't give you enough control, you can make a route that matches a regular expression using a [`RegexRoute`](../src/pjdietz/WellRESTed/Routes/RegexRoute.php). ```php $route = new RegexRoute("~/cat/[0-9]+~", "CatHandler") ``` -This will match `/cat/102` or `/cat/999` or what have you. To make this more useful, we can add a capture group. The captures are made available to the `Handler` as the `$args` member, as with the URI template variables for the `TemplateRoute` +This will match `/cat/102` or `/cat/999` or what have you. To make this more useful, we can add a capture group. The captures are made available to the handler as the `$args` member, as with the URI template variables for the [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) Note that the entire matched path will always be the `0` item, and captured groups will begin at `1`. diff --git a/src/pjdietz/WellRESTed/Handler.php b/src/pjdietz/WellRESTed/Handler.php index 17e694c..1e41491 100644 --- a/src/pjdietz/WellRESTed/Handler.php +++ b/src/pjdietz/WellRESTed/Handler.php @@ -27,7 +27,7 @@ use pjdietz\WellRESTed\Interfaces\RequestInterface; */ abstract class Handler implements HandlerInterface { - /** @var array Map of variables to suppliement the request, usually path variables. */ + /** @var array Map of variables to supplement the request, usually path variables. */ protected $args; /** @var RequestInterface The HTTP request to respond to. */ protected $request;