Skip to content

Commit d46c06d

Browse files
feat: add GapicClientTrait::prependMiddleware (#638)
1 parent ed3d420 commit d46c06d

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

src/GapicClientTrait.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ trait GapicClientTrait
7272
private string $serviceName = '';
7373
private array $agentHeader = [];
7474
private array $descriptors = [];
75+
/** @var array<callable> $prependMiddlewareCallables */
76+
private array $prependMiddlewareCallables = [];
7577
/** @var array<callable> $middlewareCallables */
7678
private array $middlewareCallables = [];
7779
private array $transportCallMethods = [
@@ -118,6 +120,42 @@ public function addMiddleware(callable $middlewareCallable): void
118120
$this->middlewareCallables[] = $middlewareCallable;
119121
}
120122

123+
/**
124+
* Prepend a middleware to the call stack by providing a callable which will be
125+
* invoked at the end of each call, and will return an instance of
126+
* {@see MiddlewareInterface} when invoked.
127+
*
128+
* The callable must have the following method signature:
129+
*
130+
* callable(MiddlewareInterface): MiddlewareInterface
131+
*
132+
* An implementation may look something like this:
133+
* ```
134+
* $client->prependMiddleware(function (MiddlewareInterface $handler) {
135+
* return new class ($handler) implements MiddlewareInterface {
136+
* public function __construct(private MiddlewareInterface $handler) {
137+
* }
138+
*
139+
* public function __invoke(Call $call, array $options) {
140+
* // modify call and options (pre-request)
141+
* $response = ($this->handler)($call, $options);
142+
* // modify the response (post-request)
143+
* return $response;
144+
* }
145+
* };
146+
* });
147+
* ```
148+
*
149+
* @param callable $middlewareCallable A callable which returns an instance
150+
* of {@see MiddlewareInterface} when invoked with a
151+
* MiddlewareInterface instance as its first argument.
152+
* @return void
153+
*/
154+
public function prependMiddleware(callable $middlewareCallable): void
155+
{
156+
$this->prependMiddlewareCallables[] = $middlewareCallable;
157+
}
158+
121159
/**
122160
* Initiates an orderly shutdown in which preexisting calls continue but new
123161
* calls are immediately cancelled.
@@ -663,6 +701,12 @@ private function createCallStack(array $callConstructionOptions)
663701
$startCallMethod = $this->transportCallMethods[$call->getCallType()];
664702
return $this->transport->$startCallMethod($call, $options);
665703
};
704+
705+
foreach ($this->prependMiddlewareCallables as $fn) {
706+
/** @var MiddlewareInterface $callStack */
707+
$callStack = $fn($callStack);
708+
}
709+
666710
$callStack = new CredentialsWrapperMiddleware($callStack, $this->credentialsWrapper);
667711
$callStack = new FixedHeaderMiddleware($callStack, $fixedHeaders, true);
668712
$callStack = new RetryMiddleware($callStack, $callConstructionOptions['retrySettings']);

tests/Unit/GapicClientTraitTest.php

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,145 @@ public function __invoke(Call $call, array $options)
13941394
$this->assertTrue($m2Called);
13951395
}
13961396

1397+
public function testPrependMiddleware()
1398+
{
1399+
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
1400+
1401+
$callOrder = [];
1402+
$middleware1 = function (callable $handler) use (&$callOrder) {
1403+
return new class($handler, $callOrder) implements MiddlewareInterface {
1404+
private $handler;
1405+
private array $callOrder;
1406+
public function __construct(
1407+
callable $handler,
1408+
array &$callOrder
1409+
) {
1410+
$this->handler = $handler;
1411+
$this->callOrder = &$callOrder;
1412+
}
1413+
public function __invoke(Call $call, array $options)
1414+
{
1415+
$this->callOrder[] = 'middleware1';
1416+
return ($this->handler)($call, $options);
1417+
}
1418+
};
1419+
};
1420+
$middleware2 = function (callable $handler) use (&$callOrder) {
1421+
return new class($handler, $callOrder) implements MiddlewareInterface {
1422+
private $handler;
1423+
private array $callOrder;
1424+
public function __construct(
1425+
callable $handler,
1426+
array &$callOrder
1427+
) {
1428+
$this->handler = $handler;
1429+
$this->callOrder = &$callOrder;
1430+
}
1431+
public function __invoke(Call $call, array $options)
1432+
{
1433+
$this->callOrder[] = 'middleware2';
1434+
return ($this->handler)($call, $options);
1435+
}
1436+
};
1437+
};
1438+
$client->addMiddleware($middleware1);
1439+
$client->prependMiddleware($middleware2);
1440+
1441+
$transport->startUnaryCall(
1442+
Argument::type(Call::class),
1443+
[
1444+
'transportOptions' => [
1445+
'custom' => ['addModifyUnaryCallableOption' => true]
1446+
],
1447+
'headers' => AgentHeader::buildAgentHeader([]),
1448+
'credentialsWrapper' => CredentialsWrapper::build([
1449+
'keyFile' => __DIR__ . '/testdata/json-key-file.json'
1450+
])
1451+
]
1452+
)
1453+
->shouldBeCalledOnce()
1454+
->willReturn(new FulfilledPromise(new Operation()));
1455+
1456+
$client->startCall(
1457+
'simpleMethod',
1458+
'decodeType',
1459+
[],
1460+
new MockRequest(),
1461+
)->wait();
1462+
1463+
$this->assertEquals(['middleware1', 'middleware2'], $callOrder);
1464+
}
1465+
1466+
public function testPrependMiddlewareOrder()
1467+
{
1468+
list($client, $transport) = $this->buildClientToTestModifyCallMethods();
1469+
1470+
$callOrder = [];
1471+
$middleware1 = function (callable $handler) use (&$callOrder) {
1472+
return new class($handler, $callOrder) implements MiddlewareInterface {
1473+
private $handler;
1474+
private array $callOrder;
1475+
public function __construct(
1476+
callable $handler,
1477+
array &$callOrder
1478+
) {
1479+
$this->handler = $handler;
1480+
$this->callOrder = &$callOrder;
1481+
}
1482+
public function __invoke(Call $call, array $options)
1483+
{
1484+
$this->callOrder[] = 'middleware1';
1485+
return ($this->handler)($call, $options);
1486+
}
1487+
};
1488+
};
1489+
$middleware2 = function (callable $handler) use (&$callOrder) {
1490+
return new class($handler, $callOrder) implements MiddlewareInterface {
1491+
private $handler;
1492+
private array $callOrder;
1493+
public function __construct(
1494+
callable $handler,
1495+
array &$callOrder
1496+
) {
1497+
$this->handler = $handler;
1498+
$this->callOrder = &$callOrder;
1499+
}
1500+
public function __invoke(Call $call, array $options)
1501+
{
1502+
$this->callOrder[] = 'middleware2';
1503+
return ($this->handler)($call, $options);
1504+
}
1505+
};
1506+
};
1507+
1508+
$client->prependMiddleware($middleware1);
1509+
$client->prependMiddleware($middleware2);
1510+
1511+
$transport->startUnaryCall(
1512+
Argument::type(Call::class),
1513+
[
1514+
'transportOptions' => [
1515+
'custom' => ['addModifyUnaryCallableOption' => true]
1516+
],
1517+
'headers' => AgentHeader::buildAgentHeader([]),
1518+
'credentialsWrapper' => CredentialsWrapper::build([
1519+
'keyFile' => __DIR__ . '/testdata/json-key-file.json'
1520+
])
1521+
]
1522+
)
1523+
->shouldBeCalledOnce()
1524+
->willReturn(new FulfilledPromise(new Operation()));
1525+
1526+
$client->startCall(
1527+
'simpleMethod',
1528+
'decodeType',
1529+
[],
1530+
new MockRequest(),
1531+
)->wait();
1532+
1533+
$this->assertEquals(['middleware2', 'middleware1'], $callOrder);
1534+
}
1535+
13971536
public function testInvalidClientOptionsTypeThrowsExceptionForV2SurfaceOnly()
13981537
{
13991538
// v1 client

0 commit comments

Comments
 (0)