diff --git a/CHANGELOG.md b/CHANGELOG.md index e83ab5a2..4623d6a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - The real request method and target url are now displayed in the profiler. - Support the cache plugin configuration for `respect_response_cache_directives`. - Extended WebProfilerToolbar item to list request with details. +- You can now copy any request as cURL command in the profiler. ### Changed diff --git a/Collector/Formatter.php b/Collector/Formatter.php index 0bb706fd..a77632fe 100644 --- a/Collector/Formatter.php +++ b/Collector/Formatter.php @@ -6,11 +6,13 @@ use Http\Client\Exception\HttpException; use Http\Client\Exception\TransferException; use Http\Message\Formatter as MessageFormatter; +use Http\Message\Formatter\CurlCommandFormatter; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; /** - * This class is a decorator for any Http\Message\Formatter with the the ability to format exceptions. + * This class is a decorator for any Http\Message\Formatter with the the ability to format exceptions and requests as + * cURL commands. * * @author Fabien Bourigault * @@ -24,11 +26,18 @@ class Formatter implements MessageFormatter private $formatter; /** - * @param MessageFormatter $formatter + * @var CurlCommandFormatter */ - public function __construct(MessageFormatter $formatter) + private $curlFormatter; + + /** + * @param MessageFormatter $formatter + * @param CurlCommandFormatter $curlFormatter + */ + public function __construct(MessageFormatter $formatter, CurlCommandFormatter $curlFormatter) { $this->formatter = $formatter; + $this->curlFormatter = $curlFormatter; } /** @@ -66,4 +75,16 @@ public function formatResponse(ResponseInterface $response) { return $this->formatter->formatResponse($response); } + + /** + * Format a RequestInterface as a cURL command. + * + * @param RequestInterface $request + * + * @return string + */ + public function formatAsCurlCommand(RequestInterface $request) + { + return $this->curlFormatter->formatRequest($request); + } } diff --git a/Collector/ProfileClient.php b/Collector/ProfileClient.php index 45a03d19..4e5be616 100644 --- a/Collector/ProfileClient.php +++ b/Collector/ProfileClient.php @@ -133,6 +133,7 @@ private function collectRequestInformations(RequestInterface $request, Stack $st $stack->setRequestScheme($request->getUri()->getScheme()); $stack->setRequestHost($request->getUri()->getHost()); $stack->setClientRequest($this->formatter->formatRequest($request)); + $stack->setCurlCommand($this->formatter->formatAsCurlCommand($request)); } /** diff --git a/Collector/Stack.php b/Collector/Stack.php index a73a165b..8ee7112b 100644 --- a/Collector/Stack.php +++ b/Collector/Stack.php @@ -81,6 +81,11 @@ final class Stack */ private $duration = 0; + /** + * @var string + */ + private $curlCommand; + /** * @param string $client * @param string $request @@ -298,4 +303,20 @@ public function setDuration($duration) { $this->duration = $duration; } + + /** + * @return string + */ + public function getCurlCommand() + { + return $this->curlCommand; + } + + /** + * @param string $curlCommand + */ + public function setCurlCommand($curlCommand) + { + $this->curlCommand = $curlCommand; + } } diff --git a/Resources/config/data-collector.xml b/Resources/config/data-collector.xml index ddd6d146..2ebdc3ce 100644 --- a/Resources/config/data-collector.xml +++ b/Resources/config/data-collector.xml @@ -10,6 +10,9 @@ + + + diff --git a/Resources/public/script/httplug.js b/Resources/public/script/httplug.js index 52ae0bd0..d48d0072 100644 --- a/Resources/public/script/httplug.js +++ b/Resources/public/script/httplug.js @@ -10,3 +10,18 @@ document.addEventListener("DOMContentLoaded", function() { }); }); }); + +/** + * Copy as cURL. + */ +document.addEventListener("DOMContentLoaded", function () { + Array.prototype.forEach.call(document.getElementsByClassName('httplug-toolbar'), function (toolbar) { + var button = toolbar.querySelector('.httplug-copy-as-curl>button'); + button.addEventListener('click', function() { + var input = toolbar.querySelector('.httplug-copy-as-curl>input'); + input.select(); + document.execCommand('copy'); + input.setSelectionRange(0, 0); + }); + }); +}) diff --git a/Resources/public/style/httplug.css b/Resources/public/style/httplug.css index a7a06141..06903af9 100644 --- a/Resources/public/style/httplug.css +++ b/Resources/public/style/httplug.css @@ -20,6 +20,46 @@ text-align: center; } +/** + * Toolbar + */ +.httplug-toolbar { + display: flex; + justify-content: space-between; +} + +.httplug-toolbar>*:not(:last-child) { + margin-right:5px; +} + +.httplug-toolbar .httplug-copy-as-curl { + flex: 1; +} + +.httplug-copy-as-curl { + font-size: 0; /*hide line return spacings*/ + display: flex; +} + +.httplug-copy-as-curl>input { + padding: .5em .75em; + border-radius: 2px 0px 0px 2px; + border: 0; + line-height: inherit; + background-color: #eee; + opacity: 1; + font-size: 14px; + flex: 1; +} + +.httplug-copy-as-curl>button { + font-size: 14px; + border-radius: 0px 2px 2px 0px; +} + +/** + * Message + */ .httplug-message { box-sizing: border-box; padding: 5px; @@ -30,6 +70,14 @@ white-space: nowrap; } +.httplug-messages { + clear: both; + display: flex; +} + +/** + * Stack header + */ .httplug-stack-header { display: flex; justify-content: space-between; @@ -59,11 +107,6 @@ font-size: 0; /*hide line return spacings*/ } -.httplug-messages { - clear: both; - display: flex; -} - .httplug-scheme-http { display: none; } diff --git a/Resources/views/webprofiler.html.twig b/Resources/views/webprofiler.html.twig index 10001f74..2f559d4f 100644 --- a/Resources/views/webprofiler.html.twig +++ b/Resources/views/webprofiler.html.twig @@ -104,7 +104,14 @@
- +
+
+ + +
+ + +

Request

@@ -116,9 +123,6 @@
{% if stack.profiles %} -
- -
{% for profile in stack.profiles %}

{{ profile.plugin }}

diff --git a/Tests/Unit/Collector/FormatterTest.php b/Tests/Unit/Collector/FormatterTest.php index defb81b8..0d18c9e3 100644 --- a/Tests/Unit/Collector/FormatterTest.php +++ b/Tests/Unit/Collector/FormatterTest.php @@ -6,6 +6,7 @@ use Http\Client\Exception\TransferException; use Http\HttplugBundle\Collector\Formatter; use Http\Message\Formatter as MessageFormatter; +use Http\Message\Formatter\CurlCommandFormatter; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -16,6 +17,11 @@ class FormatterTest extends \PHPUnit_Framework_TestCase */ private $formatter; + /** + * @var MessageFormatter + */ + private $curlFormatter; + /** * @var Formatter */ @@ -24,8 +30,9 @@ class FormatterTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->formatter = $this->getMockBuilder(MessageFormatter::class)->getMock(); + $this->curlFormatter = $this->getMockBuilder(CurlCommandFormatter::class)->getMock(); - $this->subject = new Formatter($this->formatter); + $this->subject = new Formatter($this->formatter, $this->curlFormatter); } public function testFormatRequest() @@ -88,4 +95,18 @@ public function testFormatException() $exception = new \RuntimeException('Unexpected error'); $this->assertEquals('Unexpected exception of type "RuntimeException": Unexpected error', $this->subject->formatException($exception)); } + + public function testFormatAsCurlCommand() + { + $request = $this->getMockBuilder(RequestInterface::class)->getMock(); + + $this->curlFormatter + ->expects($this->once()) + ->method('formatRequest') + ->with($this->identicalTo($request)) + ->willReturn('curl -L http://example.com') + ; + + $this->assertEquals('curl -L http://example.com', $this->subject->formatAsCurlCommand($request)); + } } diff --git a/composer.json b/composer.json index d62044b7..807b2893 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "symfony/options-resolver": "^2.8 || ^3.0", "symfony/event-dispatcher": "^2.8 || ^3.0", "symfony/framework-bundle": "^2.8 || ^3.0", - "php-http/message": "^1.3", + "php-http/message": "^1.4", "php-http/discovery": "^1.0", "twig/twig": "^1.18 || ^2.0" },