Skip to content

Commit

Permalink
Update doc (#22)
Browse files Browse the repository at this point in the history
* Update doc

Enhance documentation for handling requests with query parameters
and constructor arguments

Address the incorrect explanation of `retry` method

Add Credits section

* Reorder sections
  • Loading branch information
jenky committed Nov 28, 2023
1 parent 3075dfc commit 720ccc8
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 55 deletions.
2 changes: 1 addition & 1 deletion docs/advanced/retrying-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ $response = (new ConnectorConfigurator())

By default, failed requests are retried up to 3 times, with an exponential delay between retries (first retry = 1 second; second retry: 2 seconds, third retry: 4 seconds) and only for the following HTTP status codes: `423`, `425`, `429`, `502` and `503` when using any HTTP method and `500`, `504`, `507` and `510` when using an HTTP [idempotent method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods).

If needed, you may pass a third argument to the `Fansipan\RetryableConnector` instance. It is an instance of `Fansipan\Contracts\RetryStrategyInterface` that determines if the retries should actually be attempted. This will retries the failed requests with a delay of 1 second.
If needed, you may pass a second argument to the `retry` method. It is an instance of `Fansipan\Contracts\RetryStrategyInterface` that determines if the retries should actually be attempted. This will retries the failed requests with a delay of 1 second.

```php
use Fansipan\ConnectorConfigurator;
Expand Down
269 changes: 220 additions & 49 deletions docs/basic/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,228 @@ Unlike `AsJson` or `AsMultipart` that set the content type automatically. When s
Do not use multiple `As...` traits in your request.
!!!

## Using Constructor Arguments

You will often have variables that you want to pass into the request. You may add your own properties to your request class or use a constructor to provide variables into the request instance. Since the request is still a regular class you may customise it how you like.

Let's consider an example where you need to create a request to update a specific user based on their ID. To achieve this, you can enhance the request by adding a constructor that accepts the user ID as a parameter. By concatenating the ID variable with the endpoint, you can ensure that the ID is passed to every instance of the request. This approach allows for a more streamlined and reusable implementation.

```php
<?php

use Fansipan\Request;
use Psr\Http\Client\ClientInterface;

final class UpdateUserRequest extends Request
{
private $id;

private $data = [];

public function __construct(int $id, array $data = [])
{
$this->id = $id;
$this->data = $data;
}

public function method(): string
{
return 'PUT';
}

public function endpoint(): string
{
return '/users/'.$this->id;
}

protected function defaultBody()
{
return $this->data;
}
}

//

$request = new UpdateUserRequest(123, [
'name' => 'John Doe',
'age' => 25,
]);
```

Another example is this endpoint, [`https://jsonplaceholder.typicode.com/todos`](https://jsonplaceholder.typicode.com/todos), which supports the `_page` and `_limit` query parameters. Therefore, you should include something like this:

!!!
The flowing examples use PHP 8.1 syntax.
!!!

```php
use Fansipan\Body\AsJson;
use Fansipan\Request;

final class TodosRequest extends Request
{
use AsJson;

public function __construct(
private readonly ?int $page = null,
private readonly ?int $limit = null
) {
}

protected function defaultQuery(): array
{
return \array_filter([
'_page' => $this->page,
'_limit' => $this->limit,
]);
}
}
```

Likewise, if your endpoint has too many query strings for filtering, sorting, paging, etc., you should consider grouping them in their own dedicated value object instead of bloating the constructor.

+++ Request
```php
use Fansipan\Body\AsJson;
use Fansipan\Request;

final class TodosRequest extends Request
{
use AsJson;

public function __construct(
private readonly ?FilterQuery $filter = null,
private readonly ?SortQuery $sort = null,
private readonly ?PaginationQuery $pagination = null
) {
}

protected function defaultQuery(): array
{
return \array_filter(\array_merge([
'filter' => $this->filter->toArray(),
'sort' => $this->sort->toArray(),
], $this->pagination->toArray()));
}
}
```
+++ FilterQuery
```php
final class FilterQuery
{
public function __construct(
private ?string $name = null,
private ?string $email = null
) {
}

public function withName(string $name): self
{
$clone = clone $this;
$clone->name = $name;

return $clone;
}

public function withEmail(string $email): self
{
$clone = clone $this;
$clone->email = $email;

return $clone;
}

public function toArray(): array
{
return \array_filter([
'name' => $this->name,
'email' => $this->email,
]);
}
}
```
+++ SortQuery
```php
final class SortQuery
{
public const ASC = 'ASC';
public const DESC = 'DESC';

public function __construct(
private ?string $by = null,
private string $direction = self::DESC
) {
}

public function withSort(string $by): self
{
$clone = clone $this;
$clone->by = $by;

return $clone;
}

public function withDirection(string $direction): self
{
$clone = clone $this;
$clone->direction = $direction;

return $clone;
}

public function toArray(): array
{
if (empty($this->by)) {
return [];
}

return [
'sort' => $this->by,
'direction' => $this->direction,
];
}
}
```
+++ PaginationQuery
```php
final class PaginationQuery
{
public function __construct(
private int $page = 1,
private ?int $limit = null
) {
}

public function withPage(int $page): self
{
$clone = clone $this;
$clone->page = $page;

return $clone;
}

public function withLimit(int $limit): self
{
$clone = clone $this;
$clone->limit = $limit;

return $clone;
}

public function toArray(): array
{
return \array_filter([
'page' => $this->page,
'limit' => $this->limit,
]);
}
}
```
+++

## Modifying Request

Requests headers, query parameters and body can also be overwritten during runtime.
Requests headers, query parameters and body can also be overwritten during runtime. However it is **RECOMMENDED** to setup your request [using constructor arguments](#using-constructor-arguments) to avoid mutating the request object. This approach makes it easier for the user to know which parameters should be used for sending the request, rather than dealing with keys and values.

+++ Headers
```php
Expand Down Expand Up @@ -256,54 +475,6 @@ $request->body()->set([
```
+++

## Using Constructor Arguments

You will often have variables that you want to pass into the request. You may add your own properties to your request class or use a constructor to provide variables into the request instance. Since the request is still a regular class you may customise it how you like.

For example, I want to create a request to update an individual user by an ID. I will add a constructor to accept the user ID and I will concatenate the variable with the endpoint. This way I can pass the ID into every instance of the request.

```php
<?php

use Fansipan\Request;
use Psr\Http\Client\ClientInterface;

final class UpdateUserRequest extends Request
{
private $id;

private $data = [];

public function __construct(int $id, array $data = [])
{
$this->id = $id;
$this->data = $data;
}

public function method(): string
{
return 'PUT';
}

public function endpoint(): string
{
return '/users/'.$this->id;
}

protected function defaultBody()
{
return $this->data;
}
}

//

$request = new UpdateUserRequest(123, [
'name' => 'John Doe',
'age' => 25,
]);
```

## Sending Requests

Once you have the request instance, you can send it via connector like this:
Expand Down
3 changes: 2 additions & 1 deletion docs/basic/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
order: 300
---

All requests will return an instance of `Fansipan\Response`, which is a decorator of `Psr\Http\Message\ResponseInterface` that provides a variety of convenience methods to inspect the response:
All requests will return an instance of `Fansipan\Response`, which is a wrapper of `Psr\Http\Message\ResponseInterface` that provides a variety of convenience methods to inspect the response:

```php
$response->body(): string;
Expand All @@ -13,6 +13,7 @@ $response->successful(): bool;
$response->failed(): bool;
$response->header(string $header): ?string;
$response->headers(): array;
// and more...
```

## Throwing Exceptions On Failures
Expand Down
14 changes: 10 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ A simple package that allows you to write your API integrations or SDKs in a ele

## Why?

Building API integrations can be time consuming. After you have found an API client to use, you’re faced with lots of configuration to remember and it’s hard to repeat requests without copying and pasting. You’ll often find yourself writing the same boilerplate code over and over again.
Building API integrations can be a time-consuming task. Once you have selected an API client to work with, you encounter the challenge of managing numerous configurations. Additionally, reusing requests without resorting to copying and pasting can be difficult. It is common to find yourself writing repetitive boilerplate code repeatedly throughout the integration process.

While we have provided tools to work with APIs from [PSR-7](https://www.php-fig.org/psr/psr-7), [PSR-17](https://www.php-fig.org/psr/psr-17) and [PSR-18](https://www.php-fig.org/psr/psr-18), we do not currently have a standardized structure for building API integrations.
Tools such as [PSR-7](https://www.php-fig.org/psr/psr-7), [PSR-17](https://www.php-fig.org/psr/psr-17) and [PSR-18](https://www.php-fig.org/psr/psr-18) are great to build your API integrations. The only missing piece is how to effectively connect them together in a structured and clean manner.

## Features

Expand All @@ -23,5 +23,11 @@ While we have provided tools to work with APIs from [PSR-7](https://www.php-fig.
- Configuration is fast and can be shared across all your requests.
- [PSR](https://www.php-fig.org/psr) compliant.
- HTTP Client agnostic.
- Great for building your next PHP SDKs or package/library
- Scalable with lots of API integrations across many team members.
- Reuseable middleware.

## Credits

- [PHP-FIG](https://www.php-fig.org/) for their [PSRs](https://www.php-fig.org/psr/) with special recognition to the authors and contributors of [PSR-7 (HTTP message interfaces)](https://www.php-fig.org/psr/psr-7), [PSR-17 (HTTP Factories)](https://www.php-fig.org/psr/psr-17) and [PSR-18 (HTTP Client)](https://www.php-fig.org/psr/psr-18).
- [Guzzle](https://github.com/guzzle/guzzle) for their URI resolver and middleware concept.
- [Sam C](https://github.com/Sammyjo20) for his amazing [Saloon](https://github.com/saloonphp/saloon) package, which served as a source of inspiration.
- [HTTPlug Discovery](https://github.com/php-http/discovery).

0 comments on commit 720ccc8

Please sign in to comment.