-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from ETSGlobal/add-http-client-support
Add Symfony HttpClient support
- Loading branch information
Showing
7 changed files
with
280 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ETSGlobal\LogBundle\DependencyInjection\CompilerPass; | ||
|
||
use ETSGlobal\LogBundle\Tracing\Plugins\Symfony\HttpClientDecorator; | ||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Reference; | ||
|
||
/** | ||
* This compiler pass will create decorated instances of HttpClient. | ||
* | ||
* All services with tag `http_client.client` will be decorated by a | ||
* HttpClientDecorator so we make sure all scoped clients are taken into account. | ||
* All these original HttpClient services will be substituted by the new decorated services. | ||
*/ | ||
class HttpClientPass implements CompilerPassInterface | ||
{ | ||
private const TOKEN_COLLECTION_SERVICE_ID = 'ets_global_log.tracing.token_collection'; | ||
|
||
public function process(ContainerBuilder $container): void | ||
{ | ||
if (!$container->hasDefinition('ets_global_log.tracing.token_collection')) { | ||
throw new \RuntimeException('The token collection service definition is missing.'); | ||
} | ||
|
||
$taggedServices = $container->findTaggedServiceIds('http_client.client'); | ||
foreach ($taggedServices as $id => $attributes) { | ||
$httpClientDefinition = $container->getDefinition($id); | ||
|
||
$decorator = new Definition(HttpClientDecorator::class); | ||
$decorator->setDecoratedService($id); | ||
$decorator->setArgument('$httpClient', $httpClientDefinition); | ||
$decorator->setArgument('$tokenCollection', new Reference(self::TOKEN_COLLECTION_SERVICE_ID)); | ||
|
||
$container->setDefinition(HttpClientDecorator::class, $decorator); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
Tests/DependencyInjection/CompilerPass/HttpClientPassTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace DependencyInjection\CompilerPass; | ||
|
||
use ETSGlobal\LogBundle\DependencyInjection\CompilerPass\HttpClientPass; | ||
use ETSGlobal\LogBundle\Tracing\Plugins\Symfony\HttpClientDecorator; | ||
use ETSGlobal\LogBundle\Tracing\TokenCollection; | ||
use PHPUnit\Framework\Assert; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\HttpKernel\Kernel; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
class HttpClientPassTest extends TestCase | ||
{ | ||
/** @var ContainerBuilder */ | ||
private $containerBuilder; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->containerBuilder = new ContainerBuilder(); | ||
$this->containerBuilder->addCompilerPass(new HttpClientPass()); | ||
} | ||
|
||
public function testThrowsExceptionWhenMissingTokenCollectionService(): void | ||
{ | ||
$this->expectException(\RuntimeException::class); | ||
$this->expectExceptionMessage('The token collection service definition is missing.'); | ||
|
||
$this->containerBuilder->compile(); | ||
} | ||
|
||
public function testDoesNothingWhenNoHttpClientTag(): void | ||
{ | ||
$tokenCollectionDefinition = new Definition(TokenCollection::class); | ||
$this->containerBuilder->setDefinition('ets_global_log.tracing.token_collection', $tokenCollectionDefinition); | ||
|
||
$this->containerBuilder->compile(); | ||
|
||
Assert::assertFalse($this->containerBuilder->hasDefinition(HttpClientDecorator::class)); | ||
} | ||
|
||
public function testDecoratesHttpClient(): void | ||
{ | ||
if (version_compare(Kernel::VERSION, '4.3.0', '<=')) { | ||
$this->markTestSkipped('HttpClient is not supported until Symfony 4.3.0'); | ||
} | ||
|
||
$tokenCollectionDefinition = new Definition(TokenCollection::class); | ||
$tokenCollectionDefinition->addMethodCall('add', ['process']); | ||
$this->containerBuilder->setDefinition('ets_global_log.tracing.token_collection', $tokenCollectionDefinition); | ||
|
||
$serviceDefinition = new Definition(HttpClientInterface::class); | ||
$serviceDefinition->setPublic(true); // Avoid service being removed because never used by other services. | ||
$serviceDefinition->addTag('http_client.client'); | ||
|
||
$this->containerBuilder->setDefinition('my_client', $serviceDefinition); | ||
|
||
$this->containerBuilder->compile(); | ||
|
||
Assert::assertTrue($this->containerBuilder->hasDefinition('my_client')); | ||
|
||
$compiledDefinition = $this->containerBuilder->getDefinition('my_client'); | ||
Assert::assertSame(HttpClientDecorator::class, $compiledDefinition->getClass()); | ||
|
||
Assert::assertTrue($compiledDefinition->hasTag('http_client.client')); | ||
|
||
Assert::assertCount(2, $compiledDefinition->getArguments()); | ||
|
||
/** @var Definition $tokenCollectionArgument */ | ||
$tokenCollectionArgument = $compiledDefinition->getArgument(1); | ||
Assert::assertCount(1, $tokenCollectionArgument->getMethodCalls()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ETSGlobal\LogBundle\Tests\Tracing\Plugins\Symfony; | ||
|
||
use ETSGlobal\LogBundle\Tracing\Plugins\Symfony\HttpClientDecorator; | ||
use ETSGlobal\LogBundle\Tracing\TokenCollection; | ||
use PHPUnit\Framework\Assert; | ||
use PHPUnit\Framework\TestCase; | ||
use Prophecy\Argument; | ||
use Prophecy\PhpUnit\ProphecyTrait; | ||
use Prophecy\Prophecy\ObjectProphecy; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
use Symfony\Contracts\HttpClient\ResponseInterface; | ||
use Symfony\Contracts\HttpClient\ResponseStreamInterface; | ||
|
||
class HttpClientDecoratorTest extends TestCase | ||
{ | ||
use ProphecyTrait; | ||
|
||
/** @var HttpClientInterface|ObjectProphecy */ | ||
private $httpClientMock; | ||
|
||
/** @var TokenCollection|ObjectProphecy */ | ||
private $tokenCollectionMock; | ||
|
||
/** @var HttpClientDecorator */ | ||
private $decorator; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->httpClientMock = $this->prophesize(HttpClientInterface::class); | ||
$this->tokenCollectionMock = $this->prophesize(TokenCollection::class); | ||
$this->decorator = new HttpClientDecorator( | ||
$this->httpClientMock->reveal(), | ||
$this->tokenCollectionMock->reveal() | ||
); | ||
} | ||
|
||
public function testInjectsTokenGlobalInRequestHeaders(): void | ||
{ | ||
$options = []; | ||
$expectedOptions = [ | ||
'headers' => [ | ||
'X-Token-Global' => 'token_global', | ||
], | ||
]; | ||
|
||
$this->tokenCollectionMock | ||
->getTokenValue('global') | ||
->willReturn('token_global') | ||
; | ||
|
||
$this->httpClientMock | ||
->request('GET', 'example.com/api', $expectedOptions) | ||
->shouldBeCalled() | ||
; | ||
|
||
$this->decorator->request('GET', 'example.com/api', $options); | ||
} | ||
|
||
public function testForwardsResponse(): void | ||
{ | ||
$expectedResponse = $this->prophesize(ResponseInterface::class)->reveal(); | ||
|
||
$this->httpClientMock | ||
->request('GET', 'example.com/api', Argument::type('array')) | ||
->willReturn($expectedResponse) | ||
; | ||
|
||
$response = $this->decorator->request('GET', 'example.com/api', []); | ||
|
||
Assert::assertSame($response, $expectedResponse); | ||
} | ||
|
||
public function testForwardsStreamResponse(): void | ||
{ | ||
$expectedResponse = $this->prophesize(ResponseStreamInterface::class)->reveal(); | ||
|
||
$this->httpClientMock | ||
->stream([], 30) | ||
->willReturn($expectedResponse) | ||
; | ||
|
||
$response = $this->decorator->stream([], 30); | ||
|
||
Assert::assertSame($response, $expectedResponse); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ETSGlobal\LogBundle\Tracing\Plugins\Symfony; | ||
|
||
use ETSGlobal\LogBundle\Tracing\TokenCollection; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
use Symfony\Contracts\HttpClient\ResponseInterface; | ||
use Symfony\Contracts\HttpClient\ResponseStreamInterface; | ||
|
||
/** | ||
* Decorates a HttpClientInterface and injects the tracing token in the request. | ||
*/ | ||
class HttpClientDecorator implements HttpClientInterface | ||
{ | ||
/** @var HttpClientInterface */ | ||
private $httpClient; | ||
|
||
/** @var TokenCollection */ | ||
private $tokenCollection; | ||
|
||
public function __construct(HttpClientInterface $httpClient, TokenCollection $tokenCollection) | ||
{ | ||
$this->httpClient = $httpClient; | ||
$this->tokenCollection = $tokenCollection; | ||
} | ||
|
||
public function request(string $method, string $url, array $options = []): ResponseInterface | ||
{ | ||
$options['headers']['X-Token-Global'] = $this->tokenCollection->getTokenValue('global'); | ||
|
||
return $this->httpClient->request($method, $url, $options); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface | ||
{ | ||
return $this->httpClient->stream($responses, $timeout); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters