WellRESTed ========== [![Build Status](https://travis-ci.org/pjdietz/wellrested.svg?branch=master)](https://travis-ci.org/pjdietz/wellrested) WellRESTed is a micro-framework for creating RESTful APIs in PHP. It provides a lightweight yet powerful routing system and classes to make working with HTTP requests and responses clean and easy. Requirements ------------ - PHP 5.3 - [PHP cURL](http://php.net/manual/en/book.curl.php) for making requests with the [`Client`](src/pjdietz/WellRESTed/Client.php) class (Optional) Install ------- Add an entry for "pjdietz/wellrested" to your composer.json file's `require` property. If you are not already using Composer, create a file in your project called "composer.json" with the following content: ```json { "require": { "pjdietz/wellrested": "2.*" } } ``` Use Composer to download and install WellRESTed. Run these commands from the directory containing the **composer.json** file. ```bash $ curl -s https://getcomposer.org/installer | php $ php composer.phar install ``` You can now use WellRESTed by including the `vendor/autoload.php` file generated by Composer. Overview -------- ### Routing and Routes 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. ```php // Build the router. $router = new Router(); $router->add( ["/cats/", $catHandler], ["/dogs/*", $dogHandler], ["/hamsters/{id}", $hamsterHandler], ["~/rabbits/([0-9]+)~", $rabbitHandler] ); $router->respond(); ``` You can use `Router::add` to one route or multiple routes. When you add routes one-at-a-time, the first argument is the path pattern to match and the second argument is the handler to dispatch. (More on handlers below.) When you add mutple routes, pass an array for route where the first item is the path pattern and the second is the handler. Here's the same router added one route at a time. ```php // Build the router. $router = new Router(); // Match requests to the exact path "/cats/" $router->add("/cats/", $catHandler); // Match requests that begin with "/dogs/", including "/dogs/", "/dogs/flat-coated-retriever", etc. $router->add("/dogs/*", $dogHandler); // Match using a URI template and forward the variable to the handler. // "/hamsters/teddy-bear" will provide the handler with an array containing an "id" element with the value "teddy-bear". $router->add("/hamsters/{id}", $hamsterHandler // Match using a regular expression and forward the captures to the handler. // "/rabbit/56" will provide the handler with an array containing "56". $router->add("~/rabbits/([0-9]+)~", $rabbitHandler); // Dispatch the requset sent to the web server through the router and output the response. $router->respond(); ``` #### Route Types `Router::add()` scans the path pattern and figures out the best type of route to use. Here's a brief descripton of the types. Route Type | Example | Matches --------------- | --------------------- | -------------------------------------- `StaticRoute` | `/cats/` | Exact paths only `PrefixRoute` | `/dogs/*` | Paths beginning with a prefix `TemplateRoute` | `/hamsters/{id}` | URI template with variables `RegexRoute` | `~/rabbits/([0-9]+)~` | Regular expression Template and Regex routes will forward variables or captures to their handlers. Template routes can also be customized to restrict variables to match specific patterns. #### Route Order ##### Static Routes (Exact Matches) When the router evalutes its routes to find the best match, it first checks for a route that is an exact match to the path. The example router above will match a request to `/cats/` immediately. ##### Prefix Routes If no static routes match, the router next tries prefix routes. If the router above were to get a request for `/dogs/border-collies`, it would first check for any static routes to that path, then move on to prefix routes, where it would match and dispatch `$dogHandler`. When a router could match a request to multiple prefix routes, it always uses the longest match. Give the following router, a request for `/animals/cats/calicos/` will be matched by the second route because it is longer. ``` $myRouter->add( ["/animals/*", $animalsHandler], ["/animals/cats/*", $catsHandler] ); ``` #### Other Routes Once the router tries and fails to find a matching static or prefix route, it will try all other routes in series until it find route that matches. The router will always test them in the order you add the routes. #### Special Notes To prevent the route from interpreting your regular expression as a really weird path, a character other than `/` as the regular expression [delimiter](http://php.net/manual/en/regexp.reference.delimiters.php). For example, `~` or `#`. Template Routes can take extra parameters to indicate the characters allows in the variables. See the [wiki page about Routes](https://github.com/pjdietz/wellrested/wiki/Routes) for details. ### Handlers Matching the path is the first part of a routes job. The second part is dispatching the request to a handler. #### Callables WellRESTed provides a number of ways to create and use handlers. The simplest way to use them is to provide a callable that gets called when the route matches. ```php $router = new Router(); $router->add("/hello", function () { header("Content-type: text/plain"); echo "Hello, world"; return true; // Returning anything non-null signals the router to stop. }); $router->respond(); ``` This is okay, but a better a approach is to return an instance that implements [`ResponseInterface`](src/pjdietz/WellRESTed/Interfaces/ResponseInterface.php). ```php $router = new Router(); $router->add("/hello", function () { $response = new Response(); $response->setStatusCode(200); $response->setHeader("Content-type", "text/plain"); $response->setBody("Hello, world"); return $response; }); $router->respond(); ``` Building and returning a response object allows you to provide all of the information for your response such as the status code, headers, and body. It also makes your code more testable since you can send a mock request through the router and inspect the response it returns. ##### Callable Arguments The callable will receive to arguments when it is called. The first is an object representing the request. See [`RequestInterface`](src/pjdietz/WellRESTed/Interfaces/RequestInterface.php). ```php $router = new Router(); $router->add("/cats/", function ($rqst, $args) { // Create the response and set defaults. $response = new Response(); $response->setHeader("Content-type", "application/json"); // Determine how to respond based on the request's HTTP method. $method = $rqst->getMethod(); if ($method === "GET") { // ...Lookup the current list of cats here... $response->setStatusCode(200); $response->setBody(json_encode($catsList); } elseif ($method === "POST") { // Read from the request and store a new cat. $cat = json_decode($request->getBody()); // ...store $cat to the database... $response->setStatusCode(201); $response->setBody(json_encode($newCat); } else { $response->setStatusCode(405); } return $response; }); $router->respond(); ``` The second argument is an array of extra data. The extra data will contain things such as variables from `TemplateRoutes` or captures from `RegexRoutes`. With this router, a request to `/cats/molly` will respond with "Hello, molly". ```php $router = new Router(); $router->add("/cats/{name}", function ($rqst, $args) { $response = new Response(); $response->setStatusCode(200); $response->setHeader("Content-type", "text/plain"); $response->setBody("Hello, " . $args["name"]); return $response; }); $router->respond(); ``` You can also pass in your own custom variables by passing an array to `Router::respond` (or `Router::getResponse`). ```php $router = new Router(); $router->add("/", function ($rqst, $args) { $response = new Response(); $response->setStatusCode(200); $response->setHeader("Content-type", "text/plain"); $response->setBody("Hello, " . $args["name"]); return $response; }); $router->respond(["name" => "molly"]); ``` This is one approach to dependency injection. You can pass your dependency containter in as one of the array elements. ```php $container = new MySuperCoolDependencyContainer(); $router = new Router(); // ... Add routes ... $router->respond(["container" => container]); ``` The router will make the dependency container available to the handlers it dispatches as the "container" element of the $args array. #### HandlerInterface The callables are handy, but once you need to do something a little more significant, they start to get a bit unmanageble. Rather than returning a [`ResponseInterface`](src/pjdietz/WellRESTed/Interfaces/ResponseInterface.php), you can return a [`HandlerInterface`](src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php). The [`HandlerInterface`](src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php) is very simple and only has one method which looks like this: ```php /** * Return the handled response. * * @param RequestInterface $request The request to respond to. * @param array|null $args Optional additional arguments. * @return ResponseInterface The handled response. */ public function getResponse(RequestInterface $request, array $args = null); ``` Does it look familiar? It should! It's the same signature as the callables we've used to return responses. Let's refactor one of our callables to use a proper handler. ```php Class CatHandler implements HandlerInterface { public function getResponse(RequestInterface $request, array $args = null) { // Create the response and set defaults. $response = new Response(); $response->setHeader("Content-type", "application/json"); // Determine how to respond based on the request's HTTP method. $method = $rqst->getMethod(); if ($method === "GET") { // ...Lookup the current list of cats here... $response->setStatusCode(200); $response->setBody(json_encode($catsList); } elseif ($method === "POST") { // Read from the request and store a new cat. $cat = json_decode($request->getBody()); // ...store $cat to the database... $response->setStatusCode(201); $response->setBody(json_encode($newCat); } else { $response->setStatusCode(405); } return $response; } } $router = new Router(); $router->add("/cats/", function () { return new CatHandler(); }); $router->respond(); ``` We've pushed all the code for our handler out to a seperate class, allowing the router to just describe the actual routing. The `CatHandler` can also now have its own private methods, etc. This also gives us another chance for dependency injection. The CatHandler could be modified to take a reference to the dependency continer in its constructor. Our router could then look like this: ```php $container = new MySuperCoolDependencyContainer(); $router = new Router(); $router->add("/cats/", function () use ($container) { return new CatHandler($container); }); $router->respond(); ``` For extra fun (and more readable code), you could store the callable that provides the handler in the container. Here's an example using [Pimple](http://pimple.sensiolabs.org/). ```php $c = new Pimple\Container(); $c["catHandler"] = $c->protect(function () use ($c) { return new CatHandler($c); }); $router = new Router(); $router->add("/cats/", $c["catHandler"]); $router->respond(); ``` #### String Handlers An addional way to add handlers (previously the only way) is to provide a string containing the fully qualified name of a handler class. For example, if `CatHandler` were in the the namespace `MyApi\Handlers', the router would look like this ```php $router = new Router(); $router->add("/cats/", '\MyApi\Handlers\CatHandler'); $router->respond(); ``` The router take care of instantiating the handler for you, but it won't do it unless it is needed. In other words, a router with 100 handlers registered as strings will only ever autoload and instantiate the one handler that matches the request. #### Handler Instances The final approach to registering handlers is to just pass in a `HandlerInterface` instance. ```php $catHandler = new CatHandler(); $router = new Router(); $router->add("/cats/", $catHandler); $router->respond(); ``` This is not usually a good approach for anything other than testing becauce you need to have mutiple handlers instantiated and taking up memory even though only one will be dispatched. Prefer the callable or string approaches unless you really have a good reason. #### Handler Class WellRESTed provides the abstract class [`Handler`](src/pjdietz/WellRESTed/Handler.php) which you can subclass for your handlers. This class 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 another version of the `CatHandler` that inherits from `Handler`. ```php class CatsCollectionHandler extends \pjdietz\WellRESTed\Handler { protected function get() { // ...Lookup the current list of cats here... $this->response->setStatusCode(200); $this->response->setHeader("Content-type", "application/json"); $this->response->setBody(json_encode($catsList); } protected function post() { // Read from the instance's request member and store a new cat. $cat = json_decode($this->request->getBody()); // ...store $cat to the database... // Build a response to send to the client. $this->response->setStatusCode(201); $this->response->setHeader("Content-type", "application/json"); $this->response->setBody(json_encode($cat)); } } ``` See [Handlers](https://github.com/pjdietz/wellrested/wiki/Handlers) to learn about the subclassing the [`Handler`](src/pjdietz/WellRESTed/Handler.php) class. See [HandlerInteface](https://github.com/pjdietz/wellrested/wiki/HandlerInterface) to learn about more ways build completely custom classes. ### Responses You've already seen a [`Response`](src/pjdietz/WellRESTed/Response.php) used inside a [`Handler`](src/pjdietz/WellRESTed/Handler.php) in the examples above. You can also create 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(); $resp->setStatusCode(200); $resp->setHeader("Content-type", "text/plain"); $resp->setBody("Hello world!"); $resp->respond(); exit; ``` This will output nice response, complete with status code, headers, body. ### Requests 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 the request made to the server. $rqst = \pjdietz\WellRESTed\Request::getRequest(); if ($rqst->getMethod() === 'PUT') { $obj = json_decode($rqst->getBody()); // Do something with the JSON sent as the message body. // ... } ``` ### HTTP Client 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).) ```php // Prepare a request. $rqst = new \pjdietz\WellRESTed\Request(); $rqst->setUri('http://my.api.local/resources/'); $rqst->setMethod('POST'); $rqst->setBody(json_encode($newResource)); // Use a Client to get a Response. $client = new Client(); $resp = $client->request($rqst); // Read the response. if ($resp->getStatusCode() === 201) { // The new resource was created. $createdResource = json_decode($resp->getBody()); } ``` Copyright and License --------------------- Copyright © 2015 by PJ Dietz Licensed under the [MIT license](http://opensource.org/licenses/MIT)