Skip to content

Commit 75db051

Browse files
committed
Docs: add testing section, finalize responses section, finish create-api section
1 parent e3d235b commit 75db051

File tree

10 files changed

+407
-58
lines changed

10 files changed

+407
-58
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ composer require wrkflow/php-api-sdk-builder
66

77
> This library is still in its early stages. But the main concepts will probably remain same.
88
9+
This package is for those who wants to build wants to consume external APIs with type strict code in mind.
10+
911
## Features
1012

1113
![img](https://img.shields.io/badge/PHPStan-8-blue)
@@ -15,7 +17,7 @@ composer require wrkflow/php-api-sdk-builder
1517
- ✅ Uses PSR packages you already use for HTTP/S communication
1618
- 🏆 Forcing type strict implementation for input (request options) and output (`Response`)
1719
- 🎗 Encouraging `Data transfer objects`
18-
- 🎭 Re-usable headers using objects
20+
- 🎭 Re-usable and configurable headers using objects
1921

2022
[📖 Read the documentation](https://php-sdk-builder.wrk-flow.com)
2123

docs/content/en/architecture/architecture.md

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -134,31 +134,4 @@ class UnitAvailabilityEntity
134134
}
135135
```
136136

137-
## Concerns (utils)
138137

139-
### WorksWithJson
140-
141-
Trait that allows you to access values from an array with type safe manner.
142-
143-
```php
144-
/**
145-
* @param array<string, mixed> $data
146-
*/
147-
public function getInt(array $data, string $key): ?int;
148-
149-
/**
150-
* @param array<string, mixed> $data
151-
*/
152-
public function getFloat(array $data, string $key): ?float;
153-
154-
/**
155-
* @param array<string, mixed> $data
156-
*/
157-
public function getBool(array $data, string $key): ?bool;
158-
159-
public function floatVal(mixed $value): ?float;
160-
161-
public function intVal(mixed $value): ?int;
162-
163-
public function boolVal(mixed $value): ?bool;
164-
```

docs/content/en/architecture/headers.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Headers
33
category: Architecture
44
position: 15
5+
fullscreen: true
56
---
67

78
> Use `YourApi\Headers` namespace
@@ -14,9 +15,10 @@ can return a map of headers 'key' => 'header' or 'key' => 'headers'.
1415

1516
**Available classes (fell free to PR more):**
1617

17-
- JsonContentTypeHeaders
18-
- AcceptsJsonHeaders
19-
- JsonHeaders (sets the content type and accepts json headers)
18+
- `new JsonContentTypeHeaders()`
19+
- `new AcceptsJsonHeaders()`
20+
- `new JsonHeaders()` (sets the content type and accepts json headers)
21+
- `BearerTokenAuthorizationHeader(string $token)`
2022

2123
Each header class can chain other headers (check JsonHeaders file).
2224

docs/content/en/architecture/responses.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ You are responsible to validate the:
1212
- build the data and set it in response (can raise an exception).
1313
- build an error entity if there is an error
1414

15-
Currently, we are not throwing exceptions. Use `isSuccesfull` to determine if there is an error.
15+
Currently, we are not throwing exceptions. Use `isSuccesfull` to determine if there is an error. This allows
16+
to work with the response with any state and adds more flexibility.
1617

1718
<alert>
1819

@@ -26,32 +27,56 @@ This can probably change. Maybe raising exception is always the best option.
2627

2728
## Base implementation
2829

29-
1. Override **__construct** and try to build the response
30+
1. Override **__construct** and try to build the response (parse json / xml / etc).
3031
2. Create `private bool $isSuccessful = false;` and set it to true if the response is successful.
31-
3. Create properties and entities for your data.
32+
3. Implement `public function isSuccessful(): bool`
33+
4. Create properties and entities for your data. Use **getters** instead of properties
3234

33-
## Json responses
35+
### Consumer functions
3436

35-
To help parse JSON responses extend `AbstractJsonResponse` that will try to parse response to json and check if keys are present in an array.
37+
These functions are provided for the consumer:
38+
39+
- `isSuccessful: bool` - Indicates if the response is successfully and the data can be accessed.
40+
- `getResponse(): ResponseInterface` - Returns underlying response.
41+
42+
## JSON response
43+
44+
To help parse JSON responses extend `AbstractJsonResponse` that will try to parse response to json and check if keys are
45+
present in an array.
3646

3747
- Raises `InvalidJsonResponseException` if the response is not json.
3848
- Raises `InvalidJsonResponseException` if the of json value is invalid.
3949
- Will not raise exception if any of desired keys is missing.
40-
- Use `$this->hasKeys(json: $json, keys: [...]): bool` to check if the array contains given keys.
41-
- Use `toArray` to get json data. Will raise exception if the data is not valid.
4250

43-
### AbstractJsonItemsResponse
51+
### Consumer functions
52+
53+
These functions are provided for the consumer:
54+
55+
- `toArray(): array` - Returns the json. Will raise exception if the data is not valid.
56+
57+
### Implementation
58+
59+
- Implement `parseJson(array $json): bool;` to parse the json. Return false if not valid. **I recommend to create
60+
properties and getters instead using toArray function**.
61+
- You can use `$this->hasKeys(json: $json, keys: [...]): bool` to check if the array contains given keys.
62+
- You can use [WorksWithJson](/architecture/utils#workswithjson) to easily get values from json with proper type.
63+
64+
## JSON response with items array
4465

4566
> For step by step implementation check [Start building / create response](/start-building/create-response)
4667
47-
When you want to provide a way to easily access array items in the json using transformer and base root keys validation
68+
Use this if you want to provide a way to easily access array items in the json using transformer and base root keys
69+
validation.
70+
71+
### Consumer functions
4872

49-
You can use:
73+
These functions are provided for the consumer
5074

51-
- `getRawItems` - Returns raw items array
52-
- `items` - Will return
75+
- `getRawItems` - Returns raw items array.
76+
- `items` - Will return transformed data.
77+
- `loopItems` - Will provide a way to loop transformed items using closure.
5378

54-
#### Implementation
79+
### Implementation
5580

5681
```php
5782
/**
@@ -75,7 +100,7 @@ abstract protected function requiredRootKeys(): array;
75100
abstract protected function itemsKey(): ?string;
76101
```
77102

78-
Then implement `items` / `loopItems`. These methods are only to force you to set correct typehints.
103+
Then implement `items` / `loopItems`. These methods are only to force you to set correct typehints.
79104

80105
```php
81106
/**
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
title: Testing
3+
category: Architecture
4+
subTitle: Tools for testing.
5+
position: 16
6+
---
7+
8+
We do like unit test. We have provided basic TestCase using `Mockery` and `PHPUnit`.
9+
10+
## ResponseTestCase
11+
12+
Test case that is designed for testing responses.
13+
14+
Creates `SDKContainerFactoryContract` mock under `$this->container`.
15+
16+
### createTransformerMockViaContainer
17+
18+
Creates a mock of given transformer class and tells the container to return if it should be created.
19+
20+
### createJsonResponse
21+
22+
Returns mock for response interface that returns json array.
23+
24+
### Example
25+
26+
```php
27+
class UnitsAvailabilitiesResponseTest extends ResponseTestCase
28+
{
29+
private JsonToUnitAvailabilityEntity|MockInterface $transformer;
30+
31+
protected function setUp(): void
32+
{
33+
parent::setUp();
34+
35+
$this->transformer = $this->createTransformerMockViaContainer(
36+
JsonToUnitAvailabilityEntity::class
37+
);
38+
}
39+
40+
41+
public function testValidData(): void
42+
{
43+
$firstItem = [
44+
'ID' => 22918,
45+
'isAvailable' => true,
46+
'availabilityStatus' => [
47+
[
48+
'day' => '2016-11-21',
49+
'status' => 'A',
50+
],
51+
],
52+
];
53+
$firstItemEntity = new UnitAvailabilityEntity(
54+
22918,
55+
true,
56+
[]
57+
);
58+
59+
$this->transformer->shouldReceive('transform')
60+
->twice()
61+
->with($firstItem)
62+
->andReturn($firstItemEntity);
63+
64+
$secondItem = [
65+
'ID' => 22919,
66+
'isAvailable' => false,
67+
'availabilityStatus' => [],
68+
];
69+
$secondItemEntity = new UnitAvailabilityEntity(
70+
22919,
71+
false,
72+
[]
73+
);
74+
75+
$this->transformer->shouldReceive('transform')
76+
->twice()
77+
->with($secondItem)
78+
->andReturn($secondItemEntity);
79+
80+
$response = $this->createResponse([
81+
'from' => '2016-11-21',
82+
'to' => '2016-11-30',
83+
'items' => [
84+
$firstItem,
85+
$secondItem,
86+
],
87+
]);
88+
89+
$foundFirstEntity = false;
90+
$foundSecondEntity = false;
91+
92+
$this->assertEquals(true, $response->isSuccessful());
93+
$this->assertEquals([$firstItemEntity, $secondItemEntity], $response->items());
94+
$this->assertEquals(true, $response->loopItems(
95+
function (UnitAvailabilityEntity $entity) use (
96+
$firstItemEntity, &$foundFirstEntity, $secondItemEntity, &$foundSecondEntity
97+
) {
98+
if ($entity === $firstItemEntity) {
99+
$foundFirstEntity = true;
100+
}
101+
if ($entity === $secondItemEntity) {
102+
$foundSecondEntity = true;
103+
}
104+
})
105+
);
106+
107+
$this->assertTrue($foundFirstEntity, 'loopItems should return correct entity');
108+
$this->assertTrue($foundSecondEntity, 'loopItems should return correct entity');
109+
}
110+
111+
public function testInvalidDataGetTo(): void
112+
{
113+
$response = $this->createResponse([
114+
'test' => 'nothing',
115+
]);
116+
117+
$this->expectErrorMessage('must not be accessed before initialization');
118+
$this->assertEquals(false, $response->to);
119+
}
120+
121+
/**
122+
* @throws InvalidJsonResponseException
123+
*/
124+
protected function createResponse(array $responseData): UnitsAvailabilitiesResponse
125+
{
126+
return new UnitsAvailabilitiesResponse(
127+
$this->createJsonResponse($responseData),
128+
$this->container
129+
);
130+
}
131+
}
132+
```
133+
134+
## EndpointTestCase
135+
136+
Testcase that prepares basic API mocking:
137+
138+
- Builds `$this->uri : Uri` from with `https://localhost/test'` value.
139+
- Mocks `$this->apiFactory : ApiFactory`
140+
- Mocks `$this->api : AbstractApi` and returns `$this->uri` as a base url and `$this->apiFatory` for `factory()` calls.
141+
142+
### expectPost
143+
144+
Allows to expect a `post` call on api object. Provides a closures to validate the arguments:
145+
146+
```php
147+
/**
148+
* @param string $expectedUri expected url from the base url
149+
* @param Closure(mixed):bool|null $assertBody Checks the body. Return false if not valid.
150+
* @param Closure(mixed):bool|null $assertHeaders Checks the headers. Return false if not valid.
151+
*/
152+
protected function expectPost(
153+
string $expectedUri,
154+
?Closure $assertBody = null,
155+
?Closure $assertHeaders = null,
156+
): ExpectationInterface;
157+
```
158+
159+
### expectMakeResponse
160+
161+
Tells the container to return given response when the functions calls `container->makeResponse`.
162+
163+
```php
164+
protected function expectMakeResponse(
165+
string $expectedClass,
166+
ExpectationInterface $expectedResponse
167+
): MockInterface;
168+
```
169+
170+
### Example
171+
172+
```php
173+
class UnitsAvailabilitiesEndpointTest extends EndpointTestCase
174+
{
175+
public function testGet(): void
176+
{
177+
$options = new GetAvailabilityOptions(
178+
ids: [1],
179+
from: '2022-01-01',
180+
to: '2022-01-05'
181+
);
182+
183+
$response = $this->expectPost(
184+
self::BASE_URI . '/unitsavailabilities',
185+
function (GetAvailabilityOptions $givenOptions) use ($options) {
186+
return $options === $givenOptions;
187+
}
188+
);
189+
190+
$expectedResponse = $this->expectMakeResponse(
191+
UnitsAvailabilitiesResponse::class,
192+
$response
193+
);
194+
195+
$endpoint = new UnitsAvailabilitiesEndpoint($this->api);
196+
$result = $endpoint->get($options);
197+
198+
$this->assertSame($expectedResponse, $result);
199+
}
200+
}
201+
```

docs/content/en/architecture/utils.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
title: Utils
3+
category: Architecture
4+
subTitle: Tools for testing.
5+
position: 17
6+
---
7+
8+
## Concerns
9+
10+
Traits that helps you de-duplicate code.
11+
12+
### WorksWithJson
13+
14+
Trait that allows you to access values from an array with type safe manner.
15+
16+
```php
17+
/**
18+
* @param array<string, mixed> $data
19+
*/
20+
public function getInt(array $data, string $key): ?int;
21+
22+
/**
23+
* @param array<string, mixed> $data
24+
*/
25+
public function getFloat(array $data, string $key): ?float;
26+
27+
/**
28+
* @param array<string, mixed> $data
29+
*/
30+
public function getBool(array $data, string $key): ?bool;
31+
32+
public function floatVal(mixed $value): ?float;
33+
34+
public function intVal(mixed $value): ?int;
35+
36+
public function boolVal(mixed $value): ?bool;
37+
```

0 commit comments

Comments
 (0)