From 9f8510315135d5f131f96a0c9f5538c91bda9b5b Mon Sep 17 00:00:00 2001 From: Sam Fleming Date: Fri, 19 Oct 2018 21:49:47 +0100 Subject: [PATCH] [DX][WebProfilerBundle] Add Pretty Print functionality for Request Content --- .../Resources/views/base_js.html.twig | 4 +- .../views/Collector/request.html.twig | 23 +++++++- .../views/Profiler/base_js.html.twig | 4 +- .../DataCollector/RequestDataCollector.php | 12 +++++ .../RequestDataCollectorTest.php | 54 +++++++++++++++++++ 5 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig index 07feb3856a..858d44382c 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig @@ -41,7 +41,7 @@ /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { - var tabs = tabGroups[i].querySelectorAll('.tab'); + var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); var tabNavigation = document.createElement('ul'); tabNavigation.className = 'tab-navigation'; @@ -67,7 +67,7 @@ /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li'); + tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig index 8e496fe8a4..02d4fe5097 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig @@ -178,8 +178,27 @@

Request content not available (it was retrieved as a resource).

{% elseif collector.content %} -
-
{{ collector.content }}
+
+ {% set prettyJson = collector.isJsonRequest ? collector.prettyJson : null %} + {% if prettyJson is not null %} +
+

Pretty

+
+
+
{{ prettyJson }}
+
+
+
+ {% endif %} + +
+

Raw

+
+
+
{{ collector.content }}
+
+
+
{% else %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index ea6177e2e8..ddd6eeb0c6 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -552,7 +552,7 @@ /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { - var tabs = tabGroups[i].querySelectorAll('.tab'); + var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); var tabNavigation = document.createElement('ul'); tabNavigation.className = 'tab-navigation'; @@ -578,7 +578,7 @@ /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li'); + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 32cee4bff7..c2507b3bc6 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -263,6 +263,18 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return $this->data['content']; } + public function isJsonRequest() + { + return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); + } + + public function getPrettyJson() + { + $decoded = json_decode($this->getContent()); + + return JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, JSON_PRETTY_PRINT) : null; + } + public function getContentType() { return $this->data['content_type']; diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php index 24904f7cca..051525e3cb 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -333,4 +333,58 @@ class RequestDataCollectorTest extends TestCase throw new \InvalidArgumentException(sprintf('Cookie named "%s" is not in response', $name)); } + + /** + * @dataProvider provideJsonContentTypes + */ + public function testIsJson($contentType, $expected) + { + $response = $this->createResponse(); + $request = $this->createRequest(); + $request->headers->set('Content-Type', $contentType); + + $c = new RequestDataCollector(); + $c->collect($request, $response); + + $this->assertSame($expected, $c->isJsonRequest()); + } + + public function provideJsonContentTypes() + { + return array( + array('text/csv', false), + array('application/json', true), + array('application/JSON', true), + array('application/hal+json', true), + array('application/xml+json', true), + array('application/xml', false), + array('', false), + ); + } + + /** + * @dataProvider providePrettyJson + */ + public function testGetPrettyJsonValidity($content, $expected) + { + $response = $this->createResponse(); + $request = Request::create('/', 'POST', array(), array(), array(), array(), $content); + + $c = new RequestDataCollector(); + $c->collect($request, $response); + + $this->assertSame($expected, $c->getPrettyJson()); + } + + public function providePrettyJson() + { + return array( + array('null', 'null'), + array('{ "foo": "bar" }', '{ + "foo": "bar" +}'), + array('{ "abc" }', null), + array('', null), + ); + } }