Skip to content

Commit b7119a8

Browse files
committed
Introduce fetch API
1 parent 850595c commit b7119a8

File tree

6 files changed

+432
-4
lines changed

6 files changed

+432
-4
lines changed

composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@
4646
"php-http/vcr-plugin": "For testing HTTP clients through storing and replaying requests and responses."
4747
},
4848
"config": {
49-
"sort-packages": true
49+
"sort-packages": true,
50+
"allow-plugins": {
51+
"phpro/grumphp-shim": true
52+
}
5053
},
5154
"autoload": {
55+
"files": [
56+
"src/functions.php"
57+
],
5258
"psr-4": {
5359
"Phpro\\HttpTools\\": "src"
5460
}

src/Client/FetchClient.php

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\HttpTools\Client;
6+
7+
use Http\Client\Common\Plugin\ErrorPlugin;
8+
use Http\Client\Common\Plugin\HeaderSetPlugin;
9+
use Http\Client\Common\PluginClient;
10+
use Http\Discovery\Psr18ClientDiscovery;
11+
use Phpro\HttpTools\Request\Request;
12+
use Phpro\HttpTools\Transport\Presets\PsrPreset;
13+
use Phpro\HttpTools\Uri\RawUriBuilder;
14+
use function Psl\Dict\merge;
15+
use Psr\Http\Client\ClientInterface;
16+
17+
/**
18+
* This class is inspired on the JS fetch() function:.
19+
*
20+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
21+
*
22+
* It also contains aliases, just like axios does:
23+
* @see https://axios-http.com/docs/api_intro
24+
*
25+
* fetch(url[, config])
26+
* get(url[, config])
27+
* delete(url[, config])
28+
* head(url[, config])
29+
* options(url[, config])
30+
* post(url[, data[, config]])
31+
* put(url[, data[, config]])
32+
* patch(url[, data[, config]])
33+
*
34+
* It is linked to this package, so that you can use the transport features as well.
35+
* This makes it possible to e.g. directly parse JSON inside the fetch() function.
36+
*
37+
* @template RequestData
38+
* @template ResponseData
39+
* @psalm-type PartialConfig = array{
40+
* method ?: 'POST'|'GET'|'DELETE'|'PATCH'|'PUT'|'OPTIONS'|'HEAD',
41+
* headers ?: array<string, string>,
42+
* data ?: RequestData
43+
* transport ?: (client: ClientInterface) => TransportInterface<RequestData, ResponseData>
44+
* client ?: ClientInterface
45+
* }
46+
* @psalm-type Config = array{
47+
* method: 'POST'|'GET'|'DELETE'|'PATCH'|'PUT'|'OPTIONS'|'HEAD',
48+
* headers: array<string, string>,
49+
* data: RequestData
50+
* transport: (client: ClientInterface) => TransportInterface<RequestData, ResponseData>
51+
* client: ClientInterface
52+
* }
53+
*/
54+
final class FetchClient
55+
{
56+
/**
57+
* @var PartialConfig
58+
*/
59+
private array $config;
60+
61+
/**
62+
* @param PartialConfig $config
63+
*/
64+
public function __construct(array $config = [])
65+
{
66+
$this->config = $config;
67+
}
68+
69+
/**
70+
* @param PartialConfig $config
71+
*
72+
* @return ResponseData
73+
*/
74+
public function __invoke(string $uri, array $config)
75+
{
76+
$allConfig = $this->mergeAllConfigConfig($config);
77+
$client = $this->configureClient($allConfig);
78+
$transport = $allConfig['transport']($client);
79+
$request = new Request($allConfig['method'], $uri, [], $allConfig['data']);
80+
81+
return $transport($request);
82+
}
83+
84+
/**
85+
* @param PartialConfig $config
86+
*
87+
* @return ResponseData
88+
*/
89+
public function get(string $uri, array $config)
90+
{
91+
return ($this)($uri, $config);
92+
}
93+
94+
/**
95+
* @param PartialConfig $config
96+
*
97+
* @return ResponseData
98+
*/
99+
public function options(string $uri, array $config)
100+
{
101+
return ($this)($uri, merge(
102+
$config,
103+
[
104+
'method' => 'OPTIONS',
105+
]
106+
));
107+
}
108+
109+
/**
110+
* @param PartialConfig $config
111+
*
112+
* @return ResponseData
113+
*/
114+
public function head(string $uri, array $config)
115+
{
116+
return ($this)($uri, merge(
117+
$config,
118+
[
119+
'method' => 'OPTIONS',
120+
]
121+
));
122+
}
123+
124+
/**
125+
* @param PartialConfig $config
126+
*
127+
* @return ResponseData
128+
*/
129+
public function delete(string $uri, array $config)
130+
{
131+
return ($this)($uri, merge(
132+
$config,
133+
[
134+
'method' => 'DELETE',
135+
]
136+
));
137+
}
138+
139+
/**
140+
* @param PartialConfig $config
141+
* @param RequestData $data
142+
*
143+
* @return ResponseData
144+
*/
145+
public function post(string $uri, mixed $data, array $config)
146+
{
147+
return ($this)($uri, merge(
148+
$config,
149+
[
150+
'method' => 'POST',
151+
'data' => $data,
152+
]
153+
));
154+
}
155+
156+
/**
157+
* @param PartialConfig $config
158+
* @param RequestData $data
159+
*
160+
* @return ResponseData
161+
*/
162+
public function put(string $uri, mixed $data, array $config)
163+
{
164+
return ($this)($uri, merge(
165+
$config,
166+
[
167+
'method' => 'PUT',
168+
'data' => $data,
169+
]
170+
));
171+
}
172+
173+
/**
174+
* @param PartialConfig $config
175+
* @param RequestData $data
176+
*
177+
* @return ResponseData
178+
*/
179+
public function patch(string $uri, mixed $data, array $config)
180+
{
181+
return ($this)($uri, merge(
182+
$config,
183+
[
184+
'method' => 'PATCH',
185+
'data' => $data,
186+
]
187+
));
188+
}
189+
190+
/**
191+
* @param PartialConfig $config
192+
*
193+
* @return Config
194+
*/
195+
private function mergeAllConfigConfig(array $config): array
196+
{
197+
return merge(
198+
[
199+
'method' => 'GET',
200+
'headers' => [],
201+
'data' => null,
202+
'client' => Psr18ClientDiscovery::find(),
203+
'transport' => fn (ClientInterface $client) => PsrPreset::sync(
204+
$client,
205+
RawUriBuilder::createWithAutodiscoveredPsrFactories()
206+
),
207+
],
208+
$this->config,
209+
$config
210+
);
211+
}
212+
213+
/**
214+
* @param Config $config
215+
*/
216+
private function configureClient(array $config): ClientInterface
217+
{
218+
return new PluginClient(
219+
$config['client'],
220+
[
221+
new ErrorPlugin(),
222+
...[$config['headers'] ? new HeaderSetPlugin($config['headers']) : []],
223+
]
224+
);
225+
}
226+
}

src/Encoding/Raw/RawEncoder.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Psr\Http\Message\StreamFactoryInterface;
1111

1212
/**
13-
* @implements EncoderInterface<string>
13+
* @implements EncoderInterface<string|null>
1414
*/
1515
final class RawEncoder implements EncoderInterface
1616
{
@@ -29,10 +29,10 @@ public static function createWithAutodiscoveredPsrFactories(): self
2929
}
3030

3131
/**
32-
* @param string $data
32+
* @param string|null $data
3333
*/
3434
public function __invoke(RequestInterface $request, $data): RequestInterface
3535
{
36-
return $request->withBody($this->streamFactory->createStream($data));
36+
return $request->withBody($this->streamFactory->createStream($data ?? ''));
3737
}
3838
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\HttpTools\Transport\Presets;
6+
7+
use Amp\Promise;
8+
use Http\Client\HttpAsyncClient;
9+
use Phpro\HttpTools\Encoding\Psr7\ResponseDecoder;
10+
use Phpro\HttpTools\Encoding\Raw\RawEncoder;
11+
use Phpro\HttpTools\Transport\EncodedTransportFactory;
12+
use Phpro\HttpTools\Transport\TransportInterface;
13+
use Phpro\HttpTools\Uri\UriBuilderInterface;
14+
use Psr\Http\Client\ClientInterface;
15+
use Psr\Http\Message\ResponseInterface;
16+
17+
final class PsrPreset
18+
{
19+
/**
20+
* @return TransportInterface<string, ResponseInterface>
21+
*/
22+
public static function sync(
23+
ClientInterface $client,
24+
UriBuilderInterface $uriBuilder
25+
): TransportInterface {
26+
return EncodedTransportFactory::sync(
27+
$client,
28+
$uriBuilder,
29+
RawEncoder::createWithAutodiscoveredPsrFactories(),
30+
ResponseDecoder::createWithAutodiscoveredPsrFactories()
31+
);
32+
}
33+
34+
/**
35+
* @return TransportInterface<string, Promise<ResponseInterface>>
36+
*/
37+
public static function async(
38+
HttpAsyncClient $client,
39+
UriBuilderInterface $uriBuilder
40+
): TransportInterface {
41+
return EncodedTransportFactory::async(
42+
$client,
43+
$uriBuilder,
44+
RawEncoder::createWithAutodiscoveredPsrFactories(),
45+
ResponseDecoder::createWithAutodiscoveredPsrFactories()
46+
);
47+
}
48+
}

0 commit comments

Comments
 (0)