Skip to content

Commit

Permalink
docs: Advanced user guide
Browse files Browse the repository at this point in the history
  • Loading branch information
matapatos committed May 24, 2024
1 parent d7dc572 commit ee1c90d
Show file tree
Hide file tree
Showing 16 changed files with 386 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- Middlewares support
- IDE auto completion support
- No magic router. It uses WordPress [`register_rest_route`](https://developer.wordpress.org/reference/functions/register_rest_route/)
- Support for newer JSON schema drafts thanks to [json/opis](https://opis.io/json-schema/2.x/)
- Support for newer JSON schema drafts thanks to [opis/json-schema](https://opis.io/json-schema/2.x/)

## Requirements

Expand Down
37 changes: 37 additions & 0 deletions docs/docs/advanced-user-guide/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Each REST endpoint has its unique logic. Same goes with the data that it needs to work.

For that reason, WP-FastEndpoints provides dependency injection support for all handlers
e.g. permission handlers, main endpoint handler and middlewares.

With dependency injection our endpoints do look much cleaner ✨🧹

=== "With dependency injection"

```php
// We only need the ID. So we type $ID
$router->get('/posts/(?P<ID>[\d]+)', function ($ID) {
return get_post($ID);
});

// We don't need anything. So no arguments are defined :D
$router->get('/posts/random', function () {
$allPosts = get_posts();
return $allPosts ? $allPosts[array_rand($allPosts)] : new WpError(404, 'No posts found');
});
```

=== "No dependency injection"

```php
// Unable to fetch a dynamic parameter. Have to work with the $request argument
$router->get('/posts/(?P<ID>[\d]+)', function ($request) {
return get_post($request->get('ID'));
});

// Forced to accept $request even if not used :(
$router->get('/posts/random', function ($request) {
$allPosts = get_posts();
return $allPosts ? $allPosts[array_rand($allPosts)] : new WpError(404, 'No posts found');
});
```

6 changes: 6 additions & 0 deletions docs/docs/advanced-user-guide/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The [Quick Start](/wp-fastendpoints/quick-start/) should be able to get you a feel of
the main features of WP-FastEndpoints.

However, it's possible that the solution for your use case might not be in the Quick Start
tutorial. In the next sections we will take a look at further functionalities that
WP-FastEndpoints provides.
65 changes: 65 additions & 0 deletions docs/docs/advanced-user-guide/json-schemas/multiple-root-dirs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
For most projects, all JSON schemas might be kept inside a single root directory, like:

```text
my-plugin
└───src
│ │
│ └───Api
│ │ │
│ │ └───Schemas // Root dir
│ │ │
│ │ └───Posts
│ │ │ │ (...)
│ │ │
│ │ └───Users
│ │ │ (...)
```

However, when your API starts to grow you might end up having the need for multiple root directories.

## Example

Let's imagine that your API consists on two different versions: v1 and v2, like the following:

```text
my-plugin
└───src
│ │
│ └───Api
│ │ │
│ │ └───v1
│ │ │ └───Schemas // V1 JSON schemas root dir
│ │ │ │
│ │ │ └───Posts
│ │ │ │ (...)
│ │ │
│ │ └───v2
│ │ └───Schemas // V2 JSON schemas root dir
│ │ │
│ │ └───Posts
│ │ │ (...)
```

In this case scenario your code would look something like this:

```php
$router->appendSchemaDir(MY_PLUGIN_DIR.'Api/v1/Schemas', 'https://www.wp-fastendpoints.com/v1');
$router->appendSchemaDir(MY_PLUGIN_DIR.'Api/v2/Schemas', 'https://www.wp-fastendpoints.com/v2');
```

Then in all your endpoints you will have to specify the full schema prefix. It's important that
you specify the full prefix because we can't guarantee the order or even if the same schema
directory is returned all the time.

=== "Using v1 schemas"
```php
$router->get('/test', function(){return true;})
->returns('https://www.wp-fastendpoints.com/v1/Posts/Get.json');
```
=== "Using v2 schemas"
```php
$router->get('/test', function(){return true;})
->returns('https://www.wp-fastendpoints.com/v2/Posts/Get.json');
```
12 changes: 12 additions & 0 deletions docs/docs/advanced-user-guide/json-schemas/references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[References](https://opis.io/json-schema/2.x/references.html) is another great feature from
[opis/json-schema](https://opis.io/json-schema/2.x/).

With references, you are able to point to another JSON schema inside of schema. This can
be useful to reuse the same schema multiple times.

!!! tip
Bear in mind that when referencing a schema the full prefix must be used e.g.
*https://www.wp-fastendpoints.com/Posts/Get.json*

Take a look at their [References Docs »](https://opis.io/json-schema/2.x/references.html)
for more information
62 changes: 62 additions & 0 deletions docs/docs/advanced-user-guide/json-schemas/validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
WP-FastEndpoints uses [opis/json-schema](https://opis.io/json-schema/2.x/) for JSON schema validation.

The reason we don't use the default
[WordPress JSON schema validation functionality](https://developer.wordpress.org/reference/functions/rest_validate_value_from_schema/)
is because it's quite outdated: it only partially supports [JSON schema draft 4](https://json-schema.org/specification#migrating-from-older-drafts).
[opis/json-schema](https://opis.io/json-schema/2.x/) on the other side, does support the latest JSON schema drafts.

## Customising validator

One of the coolest features of [opis/json-schema](https://opis.io/json-schema/2.x/) is that
is super flexible, and supports:

- [Custom formats](https://opis.io/json-schema/2.x/php-format.html)
- [Custom filters](https://opis.io/json-schema/2.x/php-filter.html)
- [Custom media types](https://opis.io/json-schema/2.x/php-media-type.html) and
- [Custom content encoding](https://opis.io/json-schema/2.x/php-content-encoding.html)

These, can be super useful when ever you need some custom functionality in your JSON schemas.

### Available hooks

There are three WordPress filter hooks that you can use to customise the JSON schema validators
used in WP-FastEndpoints:

1. `fastendpoints_validator` - Triggered by both middlewares
2. `fastendpoints_schema_validator` - Only triggered for Schema middlewares validators
3. `fastendpoints_response_validator` - Only triggered for Response middlewares validators

#### Example

Imagine we only want to accept even numbers. To solve this issue, we might want to create a new custom format
for integers, called `even`, which checks if a given number is even, like:

```php
use Opis\JsonSchema\Validator;

/**
* Adds custom format resolvers to all JSON validators: request payload schema and response.
*
* @see fastendpoints_schema_validator - To update only the request payload schema validator, or
* @see fastendpoints_response_validator - To update only the response validator
*/
add_filter('fastendpoints_validator', function (Validator $validator): Validator {
$formatsResolver = $validator->parser()->getFormatResolver();
$formatsResolver->registerCallable('integer', 'even', function (int $value): bool {
return $value % 2 === 0;
});

return $validator;
});
```

Here is an example of a JSON schema using our custom `even` format:

```json
{
"type": "integer",
"format": "even"
}
```

More examples can be found in [Custom Formats docs »](https://opis.io/json-schema/2.x/php-format.html)
47 changes: 47 additions & 0 deletions docs/docs/advanced-user-guide/middlewares.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Another cool feature of WP-FastEndpoints is the support for middlewares.

Middlewares are pieces of code that can either run before and/or after a request is handled.

At this stage, you might be already familiar with both the `schema(...)` and `returns(...)`
middlewares. However, you can also create your own.

```php
use Wp\FastEndpoints\Contracts\Middleware;

class MyCustomMiddleware extends Middleware
{
/**
* Create this function if you want that your middleware is
* triggered when it receives a request and after checking
* the user permissions.
*/
public function onRequest(/* Type what you need */)
{
return;
}

/**
* Create this function when you want your middleware to be
* triggered before sending a response to the client
*/
public function onResponse(/* Type what you need */) {
return;
}
}

// Attach middleware to endpoint
$router->get('/test', function () {
return true;
})
->middleware(new MyCustomMiddleware());
```

???+ tip
You can create both methods in a middleware: `onRequest` and `onResponse`.
However, to save some CPU cycles only create the one you need [CPU emoji]

## Responses

If you need you can also take advantage of either WP_Error and WP_REST_Response to send
a direct response to the client. See [Responses page](/wp-fastendpoints/advanced-user-guide/responses)
for more info
75 changes: 75 additions & 0 deletions docs/docs/advanced-user-guide/request-life-cycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
In WP-FastEndpoints an endpoint can have multiple optional handlers attached to:

1. Permission handlers via `hasCap(...)` or `permission(...)` - Used to check for user permissions
2. Middlewares
1. Request Payload Schema Middleware via `schema(...)` - Validates the request payload
2. Response Schema Middleware via `returns(...)` - Makes sure that the proper response is sent to the client
3. Custom middlewares via `middleware(...)` - Any other custom logic that you might want to run

## Permission handlers

When a request is received the first handlers to run are the permissions handlers. Permission handlers are called
by WordPress via [`permission_callback`](https://developer.wordpress.org/rest-api/extending-the-rest-api/routes-and-endpoints/#callbacks).

In contrast to WordPress, you can have one or multiple permission handlers attached to the same endpoint.

???+ note
In the background all permission handlers are wrapped into one callable which is later on used as
`permission_callback` by the endpoint

These handlers will then be called in the same order as they were attached. For instance:

```php
$router->get('/test', function () {return true;})
->hasCap('read') # Called first
->hasCap('edit_posts') # Called second if the first one was successful
->permission('__return_true') # Called last if both the first and second were successful
```

## Middlewares

If all the permission handlers are successful the next set of handlers that run are the middlewares which
implement the `onRequest` function.

Remember that a middleware can implement `onRequest` and/or `onResponse` functions. The first one, runs before
the main endpoint handler and the later one should run after the main endpoint handler.

!!! warning
Please bear in mind that if either a [WP_Error](https://developer.wordpress.org/reference/classes/wp_error/) or
a [WP_REST_Response](https://developer.wordpress.org/reference/classes/wp_rest_response/) is returned by
the main endpoint handler following middlewares will not run. See
[Responses page](/wp-fastendpoints/advanced-user-guide/responses) for more info.

### onRequest

Same as with the permission handlers, middlewares are called with the same order that they were attached.

```php
class OnRequestMiddleware extends \Wp\FastEndpoints\Contracts\Middleware
{
public function onRequest(/* Type what you need */){
return;
}
}

$router->post('/test', function () {return true;})
->middleware(OnRequestMiddleware()) # Called first
->schema('Basics/Bool'); # Called second
```

### onResponse

Likewise, middlewares implementing onResponse functions will be triggered in the same order as they were attached.

```php
class OnResponseMiddleware extends \Wp\FastEndpoints\Contracts\Middleware
{
public function onResponse(/* Type what you need */){
return;
}
}

$router->post('/test', function () {return true;})
->returns('Basics/Bool') # Called first
->middleware(OnResponseMiddleware()); # Called second
```
52 changes: 52 additions & 0 deletions docs/docs/advanced-user-guide/responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
When building an API sometimes we want to return a response directly to the client. For example:

```php
$router->get('/posts/(?P<ID>[\d]+)', function ($ID) {
return get_post($ID);
})
->returns('Posts/Get'); // It will raise a 422 HTTP error when we are unable to find a post
```

The code above, will raise a 422 HTTP status code error when ever we are unable to find
a given post. This is where returning a message directly to the client can be useful.

## Early return

To trigger those scenarios we can either return a WP_Error or a WP_REST_Response.

=== "WP_REST_Response"
```php
$router->get('/posts/(?P<ID>[\d]+)', function ($ID) {
$post = get_post($ID);
return $post ?: new WP_REST_Response("No posts found", 404);
})
->returns('Posts/Get'); // This will not be triggered if no posts are found
```
=== "WP_Error"
```php
$router->get('/posts/(?P<ID>[\d]+)', function ($ID) {
$post = get_post($ID);
return $post ?: new WpError(404, "No posts found");
})
->returns('Posts/Get'); // This will not be triggered if no posts are found
```

### Difference between returning WP_REST_Response or WP_Error

The main difference between returning a WP_Error or a WP_REST_Response
is regarding the JSON returned in the body.

=== "WP_REST_Response"
```json
"No posts found"
```
=== "WP_Error"
```json
{
"error": 404,
"message": "No posts found",
"data": {
"status": 404
}
}
```
2 changes: 1 addition & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ hide:
- Middlewares support
- IDE auto completion support
- No magic router. It uses WordPress [`register_rest_route`](https://developer.wordpress.org/reference/functions/register_rest_route/)
- Support for newer JSON schema drafts thanks to [json/opis](https://opis.io/json-schema/2.x/)
- Support for newer JSON schema drafts thanks to [opis/json-schema](https://opis.io/json-schema/2.x/)

## Requirements

Expand Down
Loading

0 comments on commit ee1c90d

Please sign in to comment.