Skip to content

Commit 1a3ea18

Browse files
committed
Merge remote-tracking branch 'origin/main' into session-backed-enum-support
2 parents c0f0b58 + 4094802 commit 1a3ea18

File tree

241 files changed

+8092
-497
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

241 files changed

+8092
-497
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ composer.lock
66
.phpunit.result.cache
77
!tests/Foundation/fixtures/hyperf1/composer.lock
88
tests/Http/fixtures
9+
.env

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
"symfony/error-handler": "^6.3",
139139
"symfony/mailer": "^6.2",
140140
"symfony/process": "^6.2",
141+
"symfony/uid": "^7.4",
141142
"tijsverkoyen/css-to-inline-styles": "^2.2.5"
142143
},
143144
"replace": {
@@ -204,6 +205,7 @@
204205
"hyperf/redis": "~3.1.0",
205206
"hyperf/testing": "~3.1.0",
206207
"hyperf/view-engine": "~3.1.0",
208+
"hypervel/facade-documenter": "dev-main",
207209
"league/flysystem": "^3.0",
208210
"league/flysystem-aws-s3-v3": "^3.0",
209211
"league/flysystem-google-cloud-storage": "^3.0",

phpstan.neon.dist

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
parameters:
6-
level: 3
6+
level: 5
77
parallel:
88
jobSize: 20
99
maximumNumberOfProcesses: 32
@@ -26,6 +26,8 @@ parameters:
2626
- %currentWorkingDirectory%/src/support/src/Js.php
2727
- %currentWorkingDirectory%/src/notifications/src/DatabaseNotification.php
2828
ignoreErrors:
29+
# Framework traits provided for userland - not used internally but intentionally available
30+
- '#Trait Hypervel\\[A-Za-z\\\\]+ is used zero times and is not analysed\.#'
2931
- '#Result of method .* \(void\) is used\.#'
3032
- '#Unsafe usage of new static#'
3133
- '#Class [a-zA-Z0-9\\\\_]+ not found.#'

src/api-client/src/ApiClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ class ApiClient
2626
protected bool $enableMiddleware = true;
2727

2828
/**
29-
* @var array<RequestMiddleware|string>
29+
* @var array<callable|object|string>
3030
*/
3131
protected array $requestMiddleware = [];
3232

3333
/**
34-
* @var array<ResponseMiddleware|string>
34+
* @var array<callable|object|string>
3535
*/
3636
protected array $responseMiddleware = [];
3737

src/api-client/src/ApiRequest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
class ApiRequest extends HttpClientRequest
1313
{
14+
use HasContext;
15+
1416
/**
1517
* Determine if the request data has changed.
1618
*/
@@ -180,6 +182,7 @@ public function withoutHeaders(array $headers): static
180182
public function withBody(string $body): static
181183
{
182184
$this->request = $this->request->withBody(new Stream($body));
185+
$this->dataChanged = false;
183186

184187
return $this;
185188
}

src/api-client/src/ApiResource.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
use Hyperf\Contract\Arrayable;
1010
use Hyperf\Contract\Jsonable;
1111
use Hyperf\Support\Traits\ForwardsCalls;
12-
use Hypervel\HttpClient\Request;
13-
use Hypervel\HttpClient\Response;
1412
use JsonSerializable;
1513
use Stringable;
1614

@@ -25,8 +23,8 @@ class ApiResource implements Stringable, ArrayAccess, JsonSerializable, Arrayabl
2523
* Create a new resource instance.
2624
*/
2725
public function __construct(
28-
protected Response $response,
29-
protected Request $request
26+
protected ApiResponse $response,
27+
protected ApiRequest $request
3028
) {
3129
}
3230

@@ -73,12 +71,12 @@ public function __call(string $method, array $parameters): mixed
7371
return $this->forwardCallTo($this->response, $method, $parameters);
7472
}
7573

76-
public function getResponse(): Response
74+
public function getResponse(): ApiResponse
7775
{
7876
return $this->response;
7977
}
8078

81-
public function getRequest(): Request
79+
public function getRequest(): ApiRequest
8280
{
8381
return $this->request;
8482
}

src/api-client/src/ApiResponse.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,63 @@
55
namespace Hypervel\ApiClient;
66

77
use Hypervel\HttpClient\Response as HttpClientResponse;
8+
use Psr\Http\Message\StreamInterface;
89

910
class ApiResponse extends HttpClientResponse
1011
{
12+
use HasContext;
13+
14+
public function withStatus(int $code, string $reasonPhrase = ''): static
15+
{
16+
$this->response = $this->toPsrResponse()
17+
->withStatus($code, $reasonPhrase);
18+
19+
return $this;
20+
}
21+
22+
public function withProtocolVersion(string $version): static
23+
{
24+
$this->response = $this->toPsrResponse()
25+
->withProtocolVersion($version);
26+
27+
return $this;
28+
}
29+
30+
public function hasHeader(string $name): bool
31+
{
32+
return $this->toPsrResponse()
33+
->hasHeader($name);
34+
}
35+
36+
public function withHeader(string $name, mixed $value): static
37+
{
38+
$this->response = $this->toPsrResponse()
39+
->withHeader($name, $value);
40+
41+
return $this;
42+
}
43+
44+
public function withAddedHeader(string $name, mixed $value): static
45+
{
46+
$this->response = $this->toPsrResponse()
47+
->withAddedHeader($name, $value);
48+
49+
return $this;
50+
}
51+
52+
public function withoutHeader(string $name): static
53+
{
54+
$this->response = $this->toPsrResponse()
55+
->withoutHeader($name);
56+
57+
return $this;
58+
}
59+
60+
public function withBody(StreamInterface $body): static
61+
{
62+
$this->response = $this->toPsrResponse()
63+
->withBody($body);
64+
65+
return $this;
66+
}
1167
}

src/api-client/src/HasContext.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\ApiClient;
6+
7+
trait HasContext
8+
{
9+
protected array $context = [];
10+
11+
/**
12+
* Set the API request/response context.
13+
*/
14+
public function withContext(string|array $key, mixed $value = null): static
15+
{
16+
if (is_array($key)) {
17+
$this->context = array_merge($this->context, $key);
18+
19+
return $this;
20+
}
21+
22+
$this->context[$key] = $value;
23+
24+
return $this;
25+
}
26+
27+
/**
28+
* Get the API request/response context.
29+
*/
30+
public function context(?string $key = null): mixed
31+
{
32+
if ($key !== null) {
33+
return $this->context[$key] ?? null;
34+
}
35+
36+
return $this->context;
37+
}
38+
}

src/api-client/src/PendingRequest.php

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Hypervel\HttpClient\PendingRequest as ClientPendingRequest;
1010
use Hypervel\HttpClient\Request;
1111
use Hypervel\Support\Facades\Http;
12+
use Hypervel\Support\Pipeline;
1213
use Hypervel\Support\Traits\Conditionable;
1314
use InvalidArgumentException;
1415
use JsonSerializable;
@@ -34,24 +35,30 @@ class PendingRequest
3435
protected array $guzzleOptions = [];
3536

3637
/**
37-
* @var array<RequestMiddleware|string>
38+
* @var array<callable|object|string>
3839
*/
3940
protected array $requestMiddleware = [];
4041

4142
/**
42-
* @var array<ResponseMiddleware|string>
43+
* @var array<callable|object|string>
4344
*/
4445
protected array $responseMiddleware = [];
4546

4647
protected ?ClientPendingRequest $request = null;
4748

49+
protected Pipeline $pipeline;
50+
51+
protected static $cachedMiddleware = [];
52+
4853
public function __construct(
4954
protected ApiClient $client,
55+
?Pipeline $pipeline = null,
5056
) {
5157
$this->resource = $this->client->getResource();
5258
$this->enableMiddleware = $this->client->getEnableMiddleware();
5359
$this->requestMiddleware = $this->client->getRequestMiddleware();
5460
$this->responseMiddleware = $this->client->getResponseMiddleware();
61+
$this->pipeline = $pipeline ?? Pipeline::make();
5562
}
5663

5764
/**
@@ -148,7 +155,7 @@ public function withResource(string $resource): static
148155
);
149156
}
150157

151-
if (! is_subclass_of($resource, ApiResource::class)) {
158+
if (! is_subclass_of($resource, ApiResource::class)) { // @phpstan-ignore function.alreadyNarrowedType (validates PHPDoc contract at runtime)
152159
throw new InvalidArgumentException(
153160
sprintf('Resource class `%s` must be a subclass of `%s`', $resource, ApiResource::class)
154161
);
@@ -236,6 +243,14 @@ public function send(string $method, string $url, array $options = []): ApiResou
236243
return $this->sendRequest('send', $method, $url, $options);
237244
}
238245

246+
/**
247+
* Flush the cached middleware instances.
248+
*/
249+
public function flushCache(): void
250+
{
251+
static::$cachedMiddleware = [];
252+
}
253+
239254
/**
240255
* Provide a dynamic method to pass calls to the pending request.
241256
*/
@@ -255,17 +270,43 @@ protected function sendRequest(): ApiResource
255270
$request = null;
256271
$response = $this->getClient()
257272
->beforeSending(function (Request $httpRequest) use (&$request) {
258-
$request = $httpRequest;
273+
$request = new ApiRequest($httpRequest->toPsrRequest());
274+
if ($this->enableMiddleware) {
275+
$request = $this->handleMiddlewareRequest($request);
276+
}
277+
return $request->toPsrRequest();
259278
})->{$method}(...$arguments);
260279

261280
if ($response instanceof PromiseInterface) {
262281
throw new InvalidArgumentException('Api client does not support async requests');
263282
}
264283

284+
$response = new ApiResponse($response->toPsrResponse());
285+
286+
if ($this->enableMiddleware) {
287+
$response = $this->handleMiddlewareResponse($response);
288+
}
289+
265290
return $this->resource::make($response, $request);
266291
}
267292

268-
protected function createMiddleware(array $middlewareClasses, array $options): array
293+
protected function handleMiddlewareRequest(ApiRequest $request): ApiRequest
294+
{
295+
return $this->pipeline
296+
->send($request->withContext('options', $this->middlewareOptions))
297+
->through($this->createMiddleware($this->requestMiddleware))
298+
->thenReturn();
299+
}
300+
301+
protected function handleMiddlewareResponse(ApiResponse $response): ApiResponse
302+
{
303+
return $this->pipeline
304+
->send($response->withContext('options', $this->middlewareOptions))
305+
->through($this->createMiddleware($this->responseMiddleware))
306+
->thenReturn();
307+
}
308+
309+
protected function createMiddleware(array $middlewareClasses): array
269310
{
270311
$middleware = [];
271312
foreach ($middlewareClasses as $value) {
@@ -274,8 +315,11 @@ protected function createMiddleware(array $middlewareClasses, array $options): a
274315
sprintf('Middleware class `%s` does not exist', $value)
275316
);
276317
}
277-
278-
$middleware[] = new $value($this->client->getConfig(), $options);
318+
if ($cache = static::$cachedMiddleware[$value] ?? null) {
319+
$middleware[] = $cache;
320+
continue;
321+
}
322+
$middleware[] = static::$cachedMiddleware[$value] = new $value($this->client->getConfig());
279323
}
280324

281325
return $middleware;
@@ -293,18 +337,6 @@ protected function getClient(): ClientPendingRequest
293337
$request->withOptions($this->guzzleOptions);
294338
}
295339

296-
if (! $this->enableMiddleware) {
297-
return $request;
298-
}
299-
300-
foreach ($this->createMiddleware($this->requestMiddleware, $this->middlewareOptions) as $middleware) {
301-
$request->withRequestMiddleware($middleware);
302-
}
303-
304-
foreach ($this->createMiddleware($this->responseMiddleware, $this->middlewareOptions) as $middleware) {
305-
$request->withResponseMiddleware($middleware);
306-
}
307-
308340
return $this->request = $request;
309341
}
310342
}

src/api-client/src/RequestMiddleware.php

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)