Skip to content

Commit 0d8380b

Browse files
committed
refactor(authenticator): introduce applyToMiddleware method for middleware composition
- Add applyToMiddleware method to Authenticator interface to support middleware chaining - Implement applyToMiddleware in NullAuthenticator, AggregateAuthenticator, and ZohoCliq Authenticator for consistent middleware handling - Rename ZohoCliq Authenticator __invoke method to applyToMiddleware to clarify purpose - Update Authenticate middleware to use applyToMiddleware for improved middleware layering - Remove unused middleware registration from ZohoCliq Client constructor to simplify setup Signed-off-by: guanguans <[email protected]>
1 parent a088022 commit 0d8380b

File tree

10 files changed

+180
-60
lines changed

10 files changed

+180
-60
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@
283283
"rector-list-rules": "@rector list-rules",
284284
"rector-only": "@rector-clear-cache --only=Guanguans\\MonorepoBuilderWorker\\Support\\Rectors\\RenameToPsrNameRector",
285285
"rector-only-dry-run": "@rector-only --dry-run",
286-
"rector-php82": "@rector --config=rector-php82.php",
286+
"rector-php82": "@rector-clear-cache --config=rector-php82.php",
287287
"rector-php82-dry-run": "@rector-php82 --dry-run",
288288
"rector-setup-ci": "@rector setup-ci",
289289
"release": "@php ./vendor/bin/monorepo-builder release --ansi -v",

ide.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2310,6 +2310,38 @@
23102310
]
23112311
}
23122312
},
2313+
{
2314+
"complete": "staticStrings",
2315+
"condition": [
2316+
{
2317+
"classFqn": [
2318+
"\\Guanguans\\Notify\\ZohoCliq\\Messages\\AccessTokenMessage"
2319+
],
2320+
"newClassFqn": [
2321+
"\\Guanguans\\Notify\\ZohoCliq\\Messages\\AccessTokenMessage"
2322+
],
2323+
"methodNames": [
2324+
"make"
2325+
],
2326+
"parameters": [
2327+
1
2328+
],
2329+
"place": "arrayKey"
2330+
}
2331+
],
2332+
"options": {
2333+
"strings": [
2334+
"client_id",
2335+
"client_secret",
2336+
"code",
2337+
"data_center",
2338+
"grant_type",
2339+
"redirect_uri",
2340+
"refresh_token",
2341+
"scope"
2342+
]
2343+
}
2344+
},
23132345
{
23142346
"complete": "staticStrings",
23152347
"condition": [

rector-php82.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,25 @@
2424
->withPaths([
2525
__DIR__.'/src/',
2626
__DIR__.'/src/*/Authenticator.php',
27-
__DIR__.'/src/*/*/*Authenticator.php',
27+
__DIR__.'/src/Foundation/Authenticators/*Authenticator.php',
2828
])
2929
->withSkip([
3030
__DIR__.'/src/Foundation/Support',
3131
__DIR__.'/src/Foundation/Response.php',
3232
])
33-
->withoutParallel()
34-
// ->withPhpVersion(PhpVersion::PHP_82)
33+
// ->withoutParallel()
34+
->withPhpVersion(PhpVersion::PHP_82)
3535
->withRules([
3636
HasHttpClientDocCommentRector::class,
37-
// HasOptionsRector::class,
37+
HasOptionsRector::class,
3838
ToInternalExceptionRector::class,
3939
])
4040
->withConfiguredRule(AddSensitiveParameterAttributeRector::class, [
4141
AddSensitiveParameterAttributeRector::SENSITIVE_PARAMETERS => [
4242
'accessToken',
4343
'apiKey',
4444
'botApiKey',
45+
'clientSecret',
4546
'key',
4647
'password',
4748
'pushKey',

src/Foundation/Authenticators/AggregateAuthenticator.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ public function applyToRequest(RequestInterface $request): RequestInterface
4747
$request,
4848
);
4949
}
50+
51+
public function applyToMiddleware(callable $handler): callable
52+
{
53+
return array_reduce(
54+
$this->authenticators,
55+
static fn (callable $handler, Authenticator $authenticator): callable => $authenticator->applyToMiddleware($handler),
56+
$handler,
57+
);
58+
}
5059
}

src/Foundation/Authenticators/NullAuthenticator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,9 @@ public function applyToRequest(RequestInterface $request): RequestInterface
2727
{
2828
return $request;
2929
}
30+
31+
public function applyToMiddleware(callable $handler): callable
32+
{
33+
return $handler;
34+
}
3035
}

src/Foundation/Contracts/Authenticator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ interface Authenticator
2020
public function applyToOptions(array $options): array;
2121

2222
public function applyToRequest(RequestInterface $request): RequestInterface;
23+
24+
public function applyToMiddleware(callable $handler): callable;
2325
}

src/Foundation/Middleware/Authenticate.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ public function __construct(private Authenticator $authenticator) {}
2323

2424
public function __invoke(callable $handler): callable
2525
{
26-
return Middleware::mapRequest(
27-
fn (RequestInterface $request): RequestInterface => $this->authenticator->applyToRequest($request),
28-
)($handler);
26+
return $this->authenticator->applyToMiddleware(
27+
Middleware::mapRequest(
28+
fn (RequestInterface $request): RequestInterface => $this->authenticator->applyToRequest($request),
29+
)($handler)
30+
);
2931
}
3032
}

src/ZohoCliq/Authenticator.php

Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515

1616
use Guanguans\Notify\Foundation\Authenticators\NullAuthenticator;
1717
use Guanguans\Notify\Foundation\Client;
18-
use Guanguans\Notify\Foundation\Concerns\AsFormParams;
1918
use Guanguans\Notify\Foundation\Exceptions\RequestException;
20-
use Guanguans\Notify\Foundation\Message;
19+
use Guanguans\Notify\ZohoCliq\Messages\AccessTokenMessage;
2120
use GuzzleHttp\Middleware;
2221
use Psr\Http\Message\RequestInterface;
2322
use Psr\Http\Message\ResponseInterface;
2423

2524
/**
25+
* @see https://github.com/w7corp/easywechat/blob/6.x/src/OpenPlatform/ComponentAccessToken.php
26+
*
2627
* ```
2728
* curl --location 'https://accounts.zoho.com/oauth/v2/token' \
2829
* --form 'client_id="1000.TTFROV098VVFG8NB686LR98TCDR"' \
@@ -38,45 +39,76 @@
3839
*
3940
* curl --location 'https://cliq.zoho.com/api/v2/users?limit=5' \
4041
* --header 'Authorization: Zoho-oauthtoken 1000.80e9143983dcc190427cad7e8f029e25.cdc1c1043e5e7f0555fc14fc7faf3' \
41-
* ```.
42+
* ```
4243
*/
4344
class Authenticator extends NullAuthenticator
4445
{
4546
private static ?string $accessToken;
47+
private Client $client;
4648

4749
public function __construct(
4850
private string $clientId,
51+
#[\SensitiveParameter]
4952
private string $clientSecret,
50-
) {}
53+
?Client $client = null,
54+
) {
55+
$this->client = $client ?? new Client;
56+
}
5157

52-
public function __invoke(callable $handler): callable
58+
public function applyToMiddleware(callable $handler): callable
5359
{
54-
return Middleware::mapRequest(
55-
fn (RequestInterface $request): RequestInterface => $request->withHeader('Authorization', "Bearer {$this->getAccessToken()}"),
56-
)($handler);
60+
return array_reduce(
61+
[
62+
[$this, 'dataCenter'],
63+
[$this, 'retry'],
64+
[$this, 'authenticate'],
65+
],
66+
static fn (callable $handler, callable $middleware): callable => $middleware($handler),
67+
$handler,
68+
);
5769
}
5870

59-
public function retry(callable $handler): callable
71+
public static function flushAccessToken(): void
6072
{
61-
return Middleware::retry(function (int $retries, RequestInterface &$request, ?ResponseInterface $response = null): bool {
62-
if (1 <= $retries) {
63-
return false;
64-
}
65-
66-
/** @var \Guanguans\Notify\Foundation\Response $response */
67-
if ($response?->unauthorized()) {
68-
$request = $request->withHeader('Authorization', "Bearer {$this->refreshAccessToken()}");
73+
self::$accessToken = null;
74+
}
6975

70-
return true;
71-
}
76+
/**
77+
* @todo
78+
*/
79+
private function dataCenter(callable $handler): callable
80+
{
81+
return $handler;
82+
}
7283

73-
return false;
74-
})($handler);
84+
private function authenticate(callable $handler): callable
85+
{
86+
return Middleware::mapRequest(
87+
fn (RequestInterface $request): RequestInterface => $request->withHeader(
88+
'Authorization',
89+
// "Bearer xxx",
90+
"Bearer {$this->getAccessToken()}",
91+
),
92+
)($handler);
7593
}
7694

77-
public static function flushAccessToken(): void
95+
private function retry(callable $handler): callable
7896
{
79-
self::$accessToken = null;
97+
return Middleware::retry(
98+
function (int $retries, RequestInterface &$request, ?ResponseInterface $response = null): bool {
99+
if (1 <= $retries) {
100+
return false;
101+
}
102+
103+
if ($response?->getStatusCode() === 401) {
104+
$request = $request->withHeader('Authorization', "Bearer {$this->refreshAccessToken()}");
105+
106+
return true;
107+
}
108+
109+
return false;
110+
}
111+
)($handler);
80112
}
81113

82114
private function refreshAccessToken(): string
@@ -90,8 +122,6 @@ private function refreshAccessToken(): string
90122
* Temporary memory cache.
91123
*
92124
* @todo psr cache
93-
* @todo retry on authentication failure
94-
* @todo data center
95125
*/
96126
private function getAccessToken(): string
97127
{
@@ -101,31 +131,14 @@ private function getAccessToken(): string
101131
/**
102132
* @throws \GuzzleHttp\Exception\GuzzleException
103133
* @throws \JsonException
134+
* @throws \ReflectionException
104135
*/
105136
private function fetchAccessToken(): string
106137
{
107-
$response = (new Client)
108-
->send(
109-
new class([
110-
'client_id' => $this->clientId,
111-
'client_secret' => $this->clientSecret,
112-
'grant_type' => 'client_credentials',
113-
'scope' => 'ZohoCliq.Webhooks.CREATE',
114-
]) extends Message {
115-
use AsFormParams;
116-
protected array $defined = [
117-
'client_id',
118-
'client_secret',
119-
'grant_type',
120-
'scope',
121-
];
122-
123-
public function toHttpUri(): string
124-
{
125-
return 'https://accounts.zoho.com/oauth/v2/token';
126-
}
127-
}
128-
);
138+
$response = $this->client->send(AccessTokenMessage::make([
139+
'client_id' => $this->clientId,
140+
'client_secret' => $this->clientSecret,
141+
]));
129142

130143
if ($response->json('error')) {
131144
throw RequestException::create($response->request(), $response);

src/ZohoCliq/Client.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@
1313

1414
namespace Guanguans\Notify\ZohoCliq;
1515

16-
use Guanguans\Notify\Foundation\Middleware\Authenticate;
17-
use Guanguans\Notify\Foundation\Middleware\Response;
18-
1916
/**
2017
* @see https://cliq.zoho.com/integrations/webhook-tokens
2118
* @see https://www.zoho.com/cliq/help/platform/webhook-tokens.html
@@ -39,7 +36,5 @@ public function __construct(Authenticator $authenticator)
3936
{
4037
parent::__construct($authenticator);
4138
$this->baseUri('https://cliq.zoho.com/');
42-
$this->after(Authenticate::class, $authenticator, $authenticator::class.'::auth');
43-
$this->before(Response::class, [$authenticator, 'retry'], $authenticator::class.'::retry');
4439
}
4540
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright (c) 2021-2025 guanguans<[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*
11+
* @see https://github.com/guanguans/notify
12+
*/
13+
14+
namespace Guanguans\Notify\ZohoCliq\Messages;
15+
16+
use Guanguans\Notify\Foundation\Concerns\AsFormParams;
17+
use Guanguans\Notify\Foundation\Message;
18+
use Guanguans\Notify\Foundation\Support\Arr;
19+
20+
/**
21+
* @method self clientId(mixed $clientId)
22+
* @method self clientSecret(mixed $clientSecret)
23+
* @method self code(mixed $code)
24+
* @method self dataCenter(mixed $dataCenter)
25+
* @method self grantType(mixed $grantType)
26+
* @method self redirectUri(mixed $redirectUri)
27+
* @method self refreshToken(mixed $refreshToken)
28+
* @method self scope(mixed $scope)
29+
*/
30+
final class AccessTokenMessage extends Message // @internal
31+
{
32+
use AsFormParams;
33+
protected array $defined = [
34+
'data_center',
35+
36+
'client_id',
37+
'client_secret',
38+
'grant_type',
39+
'scope',
40+
41+
'code',
42+
'redirect_uri',
43+
'refresh_token',
44+
];
45+
protected array $options = [
46+
'grant_type' => 'client_credentials',
47+
'scope' => 'ZohoCliq.Webhooks.CREATE',
48+
];
49+
50+
public function toHttpUri(): string
51+
{
52+
return 'https://accounts.zoho.com/oauth/v2/token';
53+
}
54+
55+
protected function toPayload(): array
56+
{
57+
return Arr::except(parent::toPayload(), [
58+
'data_center',
59+
]);
60+
}
61+
}

0 commit comments

Comments
 (0)