Add and update documentation

This commit is contained in:
PJ Dietz 2014-07-27 17:01:18 -04:00
parent 7d3f4442b4
commit fc702b16e7
6 changed files with 301 additions and 64 deletions

View File

@ -1,34 +0,0 @@
### v1.3.0
- Allow Routers to dispatch additional Routers.
### v1.2.2
- Fix issue with 100 Continue status codes, etc. where r\n\r\n appears within the list of headers
### v1.2.1
- Allow Request to read request headers without apache_request_headers().
### v1.2.0
2013-05-27
**Warning** Some of the changes in this update will break backward compatibility.
- Remove constructor from Handler.
- Add Interfaces: RouterInterface, RouteInterface, HandlerInterface, RequestInterface, and ResponseInterface
- Classes are updated to implement interfaces and type hint interfaces in method calls. For example, Router now implements RouteInterface and its method addRoute() now type hints the parameter as a RouteInterface instead of a Route.
- Route fields pattern and handler are now private and replaced accessors to conform to RouteInterface
- Add support for port in Request. You can now use Request::setPort() and Request::getPort(), and the port is included and extract from the URI.
### v1.1.2
2013-05-19
- Change Handler to an abstract class
- Add Handler::respondWithMethodNotAllowed() for flexibility
- Convert a number of methods from protected to private
- Bug fix: return null or false from magic __get() and __isset() methods
### v1.1.1
2013-03-29
- Bug fix: Instantiate Message->headers and Message->headerLines in constructor
### v1.1.0
2013-03-26
- Add MIT License to composer.json
- Add constructor to Request to allow setting URI and method on instantiation
- Add Response::getSuccess()

View File

@ -5,6 +5,13 @@ 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. 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.
Version 2
---------
It's more RESTed than ever!
Version 2 brings a lot improvements over 1.x, but it is **not backwards compatible**. See [Changes from Version 1](documentation/changes-from-version-1.md) if you are migrating from a previous 1.x version of WellRESTed.
Requirements Requirements
------------ ------------
@ -40,7 +47,7 @@ Examples
### Routing ### 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`](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`](src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php) ) 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 path or path pattern to a class name. The class name represents the "handler" (any class implementing [`HandlerInterface`](src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php) ) 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 ```php
// Build the router. // Build the router.
@ -89,11 +96,11 @@ $router->addRoutes($routes);
$router->respond(); $router->respond();
``` ```
Notice that when you build routes through JSON, you can provide a `handlerNamespace` to be affixed to the front of every handler. When you build routes through JSON, you can provide a `handlerNamespace` to be affixed to the front of every handler.
### Handlers ### Handlers
Any class that implements [`HandlerInterface`](src/pjdietz/WellRESTed/Interfaces/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). Any class that implements [`HandlerInterface`](src/pjdietz/WellRESTed/Interfaces/HandlerInterface.php) may be the handler for a route. This could be a class that builds the actual response, or it could be another [`Router`](src/pjdietz/WellRESTed/Router.php).
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. 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.
@ -127,11 +134,12 @@ class CatsCollectionHandler extends \pjdietz\WellRESTed\Handler
} }
``` ```
See [Handlers](documentation/handlers.md) to learn about the various route classes. See [Handlers](documentation/handlers.md) to learn about the subclassing the [`Handler`](src/pjdietz/WellRESTed/Handler.php) class.
See [HandlerInteface](documentation/handler-interface.md) to learn about more ways build completely custom classes.
### Responses ### Responses
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. 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 ```php
$resp = new \pjdietz\WellRESTed\Response(); $resp = new \pjdietz\WellRESTed\Response();
@ -142,13 +150,14 @@ $resp->respond();
exit; exit;
``` ```
This will output nice response, complete with status code, headers, body.
### Requests ### 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()`. 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 ```php
// Call the static method Request::getRequest() to get a reference to the Request // Call the static method Request::getRequest() to get the request made to the server.
// singleton that represents the request made to the server.
$rqst = \pjdietz\WellRESTed\Request::getRequest(); $rqst = \pjdietz\WellRESTed\Request::getRequest();
if ($rqst->getMethod() === 'PUT') { if ($rqst->getMethod() === 'PUT') {

View File

@ -0,0 +1,103 @@
# Changes from Version 1.x
WellRESTed 2 brings a streamlined API and extra flexibility. In order to make the kinds of improvements I wanted to make, I had to take a sledge hammer to backwards compatabiliy. If you're project is using a 1.x version, please be sure to set you Composer file to use 1.x until you are ready to migrate.
```json
{
"require": {
"pjdietz/wellrested": "1.*"
}
}
```
This is not a comprehensive list, but here I'll outline some of the major departures from version 1 to help you port to the new version.
## Routes
Routes are redesigned for version 2.
### URI Templates
In version 1, `Route` included a static method for creating a route using a URI template. Version 2 has a specific class for URI template routes.
**Version 1**
```php
$route = Route::newFromUriTemplate('/things/', 'ThingsHandler');
```
**Version 2**
```php
$route = new TemplateRoute('/things/', 'ThingsHandler');
```
### Regular Expressions
Version 1's `Route` expected you to use regular expressions. To do this in version 2, use the `RegexRoute`.
**Version 1**
```php
$route = new Route('/^\/cat\//', 'CatHandler');
```
**Version 2**
```php
$route = new RegexRoute('/^\/cat\//', 'CatHandler');
```
Version 2 also includes the `StaticRoute` class for when you want to match on an exact path.
```php
$route = new StaticRoute('/cat/', 'CatHandler');
```
See [Routes](routes.md) for more information.
## Interfaces
I whittled the number of interfaces down to three:
- [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php)
- [`RequestInterface`](../src/pjdietz/WellRESTed/Interfaces/RequestInterface.php)
- [`ResponseInterface`](../src/pjdietz/WellRESTed/Interfaces/ResponseInterface.php)
(`RoutableInterface`, `RouteInterface`, `RouteTargetInterface`, `RouterInterface` are all removed.)
Version 2's design is centered around [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php). This new approach both simplifies the API, but also adds a great deal of flexibility.
See [HandlerInterface](handler-interface.md) to learn more.
## No Magic Accessors
I removed the magic property methods so that I could make better use of interfaces. This means you'll need to use accessors where you previously could have used properties.
**Version 1**
```php
$request->statusCode = 200;
```
**Version 2**
```php
$request->setStatusCode(200);
```
## Making Requests
I moved the cURL functionality that allows you to make a request out of the `Request` class and into [`Client`](../src/pjdietz/WellRESTed/Client.php).
**Version 1**
```php
// Prepare a request.
$rqst = new Request('http://my.api.local/resources/');
// Make the request.
$resp = $rqst->request();
```
**Version 2**
```php
// Prepare a request.
$rqst = new Request('http://my.api.local/resources/');
// Make the request.
$client = new Client();
$resp = $client->request(rqst);
```

View File

@ -0,0 +1,107 @@
# HandlerInterface
Much of WellRESTed 2 centers around the [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php) which has only one method that you need to implement:
```php
/**
* @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);
```
## Hello, World!
Here's a really simplistic example of a "hello world" handler.
```php
class HelloWorldHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response(200, "Hello, world!");
return $response;
}
}
```
You can plug this into a [`Router`](../src/pjdietz/WellRESTed/Router.php), and the router will always respond with "Hello, world!".
```php
$router = new Router();
$router->addRoute(new HelloWorldHandler());
$router->respond();
```
### But there's no route?
Here's the cool thing about how routing works in WellRESTed 2: The route classes implement [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php). When the `Router` iterates through its list of routes, it calls `getResponse()` on each one until it gets a non-`null` return value. At this point, it returns that response (or outputs it in the case of `Router::respond()`).
Each time the router calls `getResponse()` on a route that doesn't match request, the route returns `null` to indicate that something else will need to handle this.
Let's add another class to demonstrate.
```php
class DoNothingHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
// THE GOGGLES DO NOTHING!
return null;
}
}
```
```php
$router = new Router();
$router->addRoute(new DoNothingHandler());
$router->addRoute(new HelloWorldHandler());
$router->respond();
```
This router will still always respond with "Hello, world!" even though the router will try `DoNothingHandler` first because `DoNothingHandler` returns `null`.
### 404'ed!
If none of the routes in a router return a non-`null` value, what happens?
If you're calling `Router::respond()`, you will **always** get a response. `Router::respond()` is a shorthand method that will output the response made in `Router::getNoRouteResponse()` if it gets through its entire route table and finds no matches.
If you want to customize the default 404 response, you can either subclass `Router` and redefine `Router::getNoRouteResponse()`, or you can create a [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php) like our `HelloWorldHandler` that always returns a response with a `404 Not Found` status code and add it to the router **last**. (Remember: a router evaluates its routes in the order you add them.)
```php
class NotFoundHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
$response = new Response(400);
$response->setBody("No resource at " $request->getPath());
return $response;
}
}
```
```php
$router = new Router();
$router->addRoute(/*...Real route... */);
$router->addRoute(/*...Real route... */);
$router->addRoute(/*...Real route... */);
$router->addRoute(new NotFoundHandler());
$router->respond();
```
## Nested Routers
`Router::respond()` is a shorthand method that wraps `Router::getResponse()`, which [`Router`](../src/pjdietz/WellRESTed/Router.php) must have because it too implements [`HandlerInterface`](../src/pjdietz/WellRESTed/Interfaces/HanderInterface.php). This means that you can break your router into subrouters.
```php
$router = new Router();
$router->addRoutes(array(
new TemplateRoute("/cats/*", "CatRouter"),
new TemplateRoute("/dogs/*", "DogRouter"),
new NotFoundHandler()
));
$router->repond();
```

View File

@ -91,9 +91,7 @@ protected function buildResponse()
## HttpExceptions ## 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.php) and turns them into responses. 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.php) and turns them into responses. [`HttpException`](../src/pjdietz/WellRESTed/Exceptions/HttpExceptions.php) and its subclasses provide the status code and description for simple error responses.
[`HttpException`](../src/pjdietz/WellRESTed/Exceptions/HttpExceptions.php) 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. For example, you can throw a `NotFountException` if the resource the request indicates does not exist.
@ -132,4 +130,42 @@ Response Code | Class
`409 Conflict` | `ConflictException` `409 Conflict` | `ConflictException`
`500 Internal Server Error` | `HttpException` `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. You can also create your own by subclass [`HttpException`](../src/pjdietz/WellRESTed/Exceptions/HttpExceptions.php) and setting the exception's `$code` to the status code and `$messge` to a default message.
## Custom Base Handler
When building your API, you may want to subclass [`Handler`](../src/pjdietz/WellRESTed/Handler.php) with your own abstract class that adds methods for authenticaion, supports some extra verbs, presents custom errors, adds addiitonal headers, etc. Then, you can derive all of your concrete handlers from that class.
```php
<?php
abstract class MyHandler extends \pjdietz\WellRESTed\Handler
{
protected function buildResponse()
{
try {
// Add support for a custom HTTP verb.
switch ($this->request->getMethod()) {
case 'SNIFF':
$this->sniff();
break;
default:
self::buildResponse();
}
} catch (UnauthorizedException $e) {
// Catch 401 errors and call a method to do something with them.
$this->responseToUnauthorized($e);
}
// Add a header to all responses.
$this->response->addHeader("X-Custom-Header", "Hello, world!");
}
abstract protected function sniff();
protected function responseToUnauthorized(HttpException $e)
{
$this->response->setStatusCode($e->getCode());
$this->response->setBody("Y U NO SEND CREDENTIALS?");
}
}
```

View File

@ -16,7 +16,7 @@ Use a [`StaticRoute`](../src/pjdietz/WellRESTed/Routes/StaticRoute.php) when you
$route = new StaticRoute("/cats/", "CatHandler"); $route = new StaticRoute("/cats/", "CatHandler");
``` ```
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. 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 for `/cats/`, `/dogs/`, and `/birds/`. You can make this by passing an array instead of a string as the first parameter.
```php ```php
$route = new StaticRoute(array("/cats/", "/dogs/", "/birds/"), "AnimalHandler"); $route = new StaticRoute(array("/cats/", "/dogs/", "/birds/"), "AnimalHandler");
@ -32,7 +32,9 @@ 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"); $route = new TemplateRoute("/cats/{id}", "CatItemHandler");
``` ```
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. This will match `/cats/1`, `/cats/99`, `/cats/molly`, etc.
A [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) uses 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 ```php
class CatItemHandlder extends \pjdietz\WellRESTed\Handler class CatItemHandlder extends \pjdietz\WellRESTed\Handler
@ -46,17 +48,16 @@ class CatItemHandlder extends \pjdietz\WellRESTed\Handler
} }
``` ```
For the paths `/cats/1`, `/cats/99`, `/cats/molly`, the value of `$this->args["id"]` will be `"1"`, `"99"`, or `"molly"`.
Your template may have multiple variables. Be sure to give each a unique name. Your template may have multiple variables. Be sure to give each a unique name.
With this [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php)... Here the handler will have access to `$this->args["catId"]` and `$this->args["dogId"]`.
```php ```php
$route = new TemplateRoute("/cats/{catId}/{dogId}", "CatItemHandler"); $route = new TemplateRoute("/cats/{catId}/{dogId}", "CatItemHandler");
``` ```
...the handler will have access to `$this->args["catId"]` and `$this->args["dogId"]`.
### Default Variable Pattern ### Default Variable Pattern
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. 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.
@ -65,16 +66,19 @@ By default, the [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute
$route = new TemplateRoute("/cats/{id}", "CatItemHandler", TemplateRoute::RE_NUM); $route = new TemplateRoute("/cats/{id}", "CatItemHandler", TemplateRoute::RE_NUM);
``` ```
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. This will match `/cats/1` or `/cats/99`, but NOT `/cats/molly`.
### Pattern Constants ### Pattern Constants
| Constant | Pattern | Description | 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.
| --------- | ----------------- | ----------- |
| `RE_SLUG` | `[0-9a-zA-Z\-_]+` | "URL-friendly" characters such as numbers, letters, underscores, and hyphens |
| `RE_NUM` | `[0-9]+` | Digits only | | Constant | Pattern | Description |
| `RE_ALPHA` | `[a-zA-Z]+` | Letters only | | ------------ | ----------------- | ----------- |
| `RE_ALPHANUM` | `[0-9a-zA-Z]+` | Letters and digits | | `RE_SLUG` | `[0-9a-zA-Z\-_]+` | **(Default)** "URL-friendly" characters such as numbers, letters, underscores, and hyphens |
| `RE_NUM` | `[0-9]+` | Digits only |
| `RE_ALPHA` | `[a-zA-Z]+` | Letters only |
| `RE_ALPHANUM` | `[0-9a-zA-Z]+` | Letters and digits |
### Variable Patterns Array ### Variable Patterns Array
@ -88,13 +92,25 @@ $patterns = array(
$route = new TemplateRoute( $route = new TemplateRoute(
"/cats/{id}/{name}/{more}", "/cats/{id}/{name}/{more}",
"CatItemHandler", "CatItemHandler",
TemplateRoute::RE_SLUG, TemplateRoute::RE_ALPHANUM,
$patterns); $patterns);
``` ```
Here, `{id}` will need to match digits and `{name}` must be all letters. Since `{more}` is not explicitly provided in the `$patterns` array, it uses the default `TemplateRoute::RE_SLUG` passed as the third parameter. Here, `{id}` will need to match digits and `{name}` must be all letters. Since `{more}` is not explicitly provided in the `$patterns` array, it uses the default `TemplateRoute::RE_ALPHANUM` passed as the third parameter.
### RegexRoute ### Wildcard
If you want to match all requests with paths that start with a given template, end your template with `*`. This is useful for handing groups of requests off to subrouters.
```php
$route = new TemplateRoute("/cats/*", "CatRouter");
```
This will match `/cats/`, `/cats/21`, `/cats/with/extra/path/components/`, etc.
The `*` wildcard may only appear at the **end** of your template.
## RegexRoute
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). 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).
@ -102,7 +118,7 @@ If [`TemplateRoute`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) doesn't
$route = new RegexRoute("~/cat/[0-9]+~", "CatHandler") $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`](../src/pjdietz/WellRESTed/Routes/TemplateRoute.php) This will match `/cat/102`, `/cat/999`, etc. 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`. Note that the entire matched path will always be the `0` item, and captured groups will begin at `1`.
@ -122,14 +138,14 @@ Array
) )
``` ```
You can also used named capture groups like this; You can also used named capture groups like this:
```php ```php
$route = new RegexRoute("~/cat/(?<id>[0-9]+)~", "CatHandler") $route = new RegexRoute("~/cat/(?<id>[0-9]+)~", "CatHandler")
``` ```
...with the path `/cat/99` creates this array or matches: The path `/cat/99` creates this array of matches:
``` ```
Array Array