diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d70bb0c..3d7c1298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## Unreleased + +### Added + +- The real request method and target url are now displayed in the profiler. + ## 1.4.0 - 2017-02-21 ### Changed diff --git a/Collector/ProfileClient.php b/Collector/ProfileClient.php new file mode 100644 index 00000000..297d6296 --- /dev/null +++ b/Collector/ProfileClient.php @@ -0,0 +1,83 @@ + + * + * @internal + */ +class ProfileClient implements HttpClient, HttpAsyncClient +{ + /** + * @var HttpClient|HttpAsyncClient + */ + private $client; + + /** + * @var Collector + */ + private $collector; + + /** + * @param HttpClient|HttpAsyncClient $client The client to profile. Client must implement both HttpClient and + * HttpAsyncClient interfaces. + * @param Collector $collector + */ + public function __construct($client, Collector $collector) + { + if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) { + throw new \RuntimeException(sprintf( + '%s first argument must implement %s and %s. Consider using %s.', + __METHOD__, + HttpClient::class, + HttpAsyncClient::class, + FlexibleHttpClient::class + )); + } + $this->client = $client; + $this->collector = $collector; + } + + /** + * {@inheritdoc} + */ + public function sendAsyncRequest(RequestInterface $request) + { + $this->collectRequestInformations($request); + + return $this->client->sendAsyncRequest($request); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + $this->collectRequestInformations($request); + + return $this->client->sendRequest($request); + } + + /** + * @param RequestInterface $request + */ + private function collectRequestInformations(RequestInterface $request) + { + if (!$stack = $this->collector->getCurrentStack()) { + return; + } + + $stack = $this->collector->getCurrentStack(); + $stack->setRequestTarget($request->getRequestTarget()); + $stack->setRequestMethod($request->getMethod()); + } +} diff --git a/Collector/ProfileClientFactory.php b/Collector/ProfileClientFactory.php new file mode 100644 index 00000000..46ddc1fb --- /dev/null +++ b/Collector/ProfileClientFactory.php @@ -0,0 +1,55 @@ + + * + * @internal + */ +class ProfileClientFactory implements ClientFactory +{ + /** + * @var ClientFactory|callable + */ + private $factory; + + /** + * @var Collector + */ + private $collector; + + /** + * @param ClientFactory|callable $factory + * @param Collector $collector + */ + public function __construct($factory, Collector $collector) + { + if (!$factory instanceof ClientFactory && !is_callable($factory)) { + throw new \RuntimeException(sprintf('First argument to ProfileClientFactory::__construct must be a "%s" or a callable.', ClientFactory::class)); + } + $this->factory = $factory; + $this->collector = $collector; + } + + /** + * {@inheritdoc} + */ + public function createClient(array $config = []) + { + $client = is_callable($this->factory) ? $this->factory($config) : $this->factory->createClient($config); + + if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) { + $client = new FlexibleHttpClient($client); + } + + return new ProfileClient($client, $this->collector); + } +} diff --git a/Collector/Stack.php b/Collector/Stack.php index d4788396..77be7c5a 100644 --- a/Collector/Stack.php +++ b/Collector/Stack.php @@ -36,6 +36,16 @@ final class Stack */ private $failed = false; + /** + * @var string + */ + private $requestTarget; + + /** + * @var string + */ + private $requestMethod; + /** * @param string $client * @param string $request @@ -109,4 +119,36 @@ public function setFailed($failed) { $this->failed = $failed; } + + /** + * @return string + */ + public function getRequestTarget() + { + return $this->requestTarget; + } + + /** + * @param string $requestTarget + */ + public function setRequestTarget($requestTarget) + { + $this->requestTarget = $requestTarget; + } + + /** + * @return string + */ + public function getRequestMethod() + { + return $this->requestMethod; + } + + /** + * @param string $requestMethod + */ + public function setRequestMethod($requestMethod) + { + $this->requestMethod = $requestMethod; + } } diff --git a/Resources/config/data-collector.xml b/Resources/config/data-collector.xml index a7e2b0d0..7c814014 100644 --- a/Resources/config/data-collector.xml +++ b/Resources/config/data-collector.xml @@ -29,5 +29,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Resources/views/webprofiler.html.twig b/Resources/views/webprofiler.html.twig index 689ab89d..62d6aa1a 100644 --- a/Resources/views/webprofiler.html.twig +++ b/Resources/views/webprofiler.html.twig @@ -62,7 +62,7 @@ {% for stack in collector.clientStacks(client) %}

- Request #{{ loop.index }} + Request #{{ loop.index }} - {{ stack.requestMethod }} {{ stack.requestTarget }} {% if stack.failed %} - Errored {% endif %} diff --git a/Tests/Unit/Collector/ProfileClientTest.php b/Tests/Unit/Collector/ProfileClientTest.php new file mode 100644 index 00000000..0c2e526f --- /dev/null +++ b/Tests/Unit/Collector/ProfileClientTest.php @@ -0,0 +1,108 @@ +collector = $this->getMockBuilder(Collector::class)->disableOriginalConstructor()->getMock(); + $this->currentStack = new Stack('default', 'FormattedRequest'); + $this->client = $this->getMockBuilder(ClientInterface::class)->getMock(); + $this->request = $this->getMockBuilder(RequestInterface::class)->getMock(); + $this->subject = new ProfileClient($this->client, $this->collector); + $this->response = $this->getMockBuilder(ResponseInterface::class)->getMock(); + $this->promise = $this->getMockBuilder(Promise::class)->getMock(); + + $this->client->method('sendRequest')->willReturn($this->response); + $this->client->method('sendAsyncRequest')->willReturn($this->promise); + + $this->request->method('getMethod')->willReturn('GET'); + $this->request->method('getRequestTarget')->willReturn('/target'); + + $this->collector->method('getCurrentStack')->willReturn($this->currentStack); + } + + public function testCallDecoratedClient() + { + $this->client + ->expects($this->once()) + ->method('sendRequest') + ->with($this->identicalTo($this->request)) + ; + + $this->client + ->expects($this->once()) + ->method('sendAsyncRequest') + ->with($this->identicalTo($this->request)) + ; + + $this->assertEquals($this->response, $this->subject->sendRequest($this->request)); + + $this->assertEquals($this->promise, $this->subject->sendAsyncRequest($this->request)); + } + + public function testCollectRequestInformations() + { + $this->subject->sendRequest($this->request); + + $this->assertEquals('GET', $this->currentStack->getRequestMethod()); + $this->assertEquals('/target', $this->currentStack->getRequestTarget()); + } + + public function testCollectAsyncRequestInformations() + { + $this->subject->sendAsyncRequest($this->request); + + $this->assertEquals('GET', $this->currentStack->getRequestMethod()); + $this->assertEquals('/target', $this->currentStack->getRequestTarget()); + } +} + +interface ClientInterface extends HttpClient, HttpAsyncClient +{ +}