Skip to content

Commit 4760142

Browse files
committed
Merge pull request #19 from Nyholm/toolbar
Added web profiler and toolbar
2 parents 447ac6c + d32e4b1 commit 4760142

File tree

11 files changed

+258
-23
lines changed

11 files changed

+258
-23
lines changed

Collector/MessageJournal.php

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\Collector;
4+
5+
use Http\Client\Exception;
6+
use Http\Client\Plugin\Journal;
7+
use Http\Message\Formatter;
8+
use Http\Message\Formatter\SimpleFormatter;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
use Symfony\Component\HttpFoundation\Request;
12+
use Symfony\Component\HttpFoundation\Response;
13+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
14+
15+
/**
16+
* @author Tobias Nyholm <[email protected]>
17+
*/
18+
class MessageJournal extends DataCollector implements Journal
19+
{
20+
/**
21+
* @var Formatter
22+
*/
23+
private $formatter;
24+
25+
/**
26+
* @param Formatter $formatter
27+
*/
28+
public function __construct(Formatter $formatter = null)
29+
{
30+
$this->formatter = $formatter ?: new SimpleFormatter();
31+
$this->data = ['success' => [], 'failure' => []];
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function addSuccess(RequestInterface $request, ResponseInterface $response)
38+
{
39+
$this->data['success'][] = [
40+
'request' => $this->formatter->formatRequest($request),
41+
'response' => $this->formatter->formatResponse($response),
42+
];
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function addFailure(RequestInterface $request, Exception $exception)
49+
{
50+
if ($exception instanceof Exception\HttpException) {
51+
$formattedResponse = $this->formatter->formatResponse($exception->getResponse());
52+
} elseif ($exception instanceof Exception\TransferException) {
53+
$formattedResponse = $exception->getMessage();
54+
} else {
55+
$formattedResponse = sprintf('Unexpected exception of type "%s"', get_class($exception));
56+
}
57+
58+
$this->data['failure'][] = [
59+
'request' => $this->formatter->formatRequest($request),
60+
'response' => $formattedResponse,
61+
];
62+
}
63+
64+
/**
65+
* Get the successful request-resonse pairs.
66+
*
67+
* @return array
68+
*/
69+
public function getSucessfulRequests()
70+
{
71+
return $this->data['success'];
72+
}
73+
74+
/**
75+
* Get the failed request-resonse pairs.
76+
*
77+
* @return array
78+
*/
79+
public function getFailedRequests()
80+
{
81+
return $this->data['failure'];
82+
}
83+
84+
/**
85+
* Get the total number of request made.
86+
*
87+
* @return int
88+
*/
89+
public function getTotalRequests()
90+
{
91+
return count($this->data['success']) + count($this->data['failure']);
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public function collect(Request $request, Response $response, \Exception $exception = null)
98+
{
99+
// We do not need to collect any data form the Symfony Request and Response
100+
}
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public function getName()
106+
{
107+
return 'httplug';
108+
}
109+
}

DependencyInjection/Configuration.php

+14-4
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ public function getConfigTreeBuilder()
3434
return !empty($v['classes']['client'])
3535
|| !empty($v['classes']['message_factory'])
3636
|| !empty($v['classes']['uri_factory'])
37-
|| !empty($v['classes']['stream_factory'])
38-
;
37+
|| !empty($v['classes']['stream_factory']);
3938
})
4039
->then(function ($v) {
4140
foreach ($v['classes'] as $key => $class) {
@@ -72,8 +71,19 @@ public function getConfigTreeBuilder()
7271
->scalarNode('stream_factory')->defaultNull()->end()
7372
->end()
7473
->end()
75-
->end()
76-
;
74+
->arrayNode('toolbar')
75+
->addDefaultsIfNotSet()
76+
->info('Extend the debug profiler with inforation about requests.')
77+
->children()
78+
->enumNode('enabled')
79+
->info('If "auto" (default), the toolbar is activated when kernel.debug is true. You can force the toolbar on and off by changing this option.')
80+
->values([true, false, 'auto'])
81+
->defaultValue('auto')
82+
->end()
83+
->scalarNode('formatter')->defaultNull()->end()
84+
->end()
85+
->end()
86+
->end();
7787

7888
return $treeBuilder;
7989
}

DependencyInjection/HttplugExtension.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace Http\HttplugBundle\DependencyInjection;
44

55
use Http\HttplugBundle\ClientFactory\DummyClient;
6-
use Symfony\Component\DependencyInjection\ContainerBuilder;
76
use Symfony\Component\Config\FileLocator;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
88
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
99
use Symfony\Component\DependencyInjection\Reference;
1010
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@@ -27,6 +27,18 @@ public function load(array $configs, ContainerBuilder $container)
2727
$loader->load('services.xml');
2828
$loader->load('plugins.xml');
2929
$loader->load('discovery.xml');
30+
31+
$enabled = is_bool($config['toolbar']['enabled']) ? $config['toolbar']['enabled'] : $container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug');
32+
if ($enabled) {
33+
$loader->load('data-collector.xml');
34+
$config['_inject_collector_plugin'] = true;
35+
36+
if (!empty($config['toolbar']['formatter'])) {
37+
$container->getDefinition('httplug.collector.message_journal')
38+
->replaceArgument(0, new Reference($config['toolbar']['formatter']));
39+
}
40+
}
41+
3042
foreach ($config['classes'] as $service => $class) {
3143
if (!empty($class)) {
3244
$container->removeDefinition(sprintf('httplug.%s.default', $service));
@@ -54,6 +66,10 @@ protected function configureClients(ContainerBuilder $container, array $config)
5466
$first = $name;
5567
}
5668

69+
if (isset($config['_inject_collector_plugin'])) {
70+
array_unshift($arguments['plugins'], 'httplug.collector.history_plugin');
71+
}
72+
5773
$def = $container->register('httplug.client.'.$name, DummyClient::class);
5874

5975
if (empty($arguments['plugins'])) {

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ For information how to write applications with the services provided by this bun
5151
| httplug.stream_factory | Service* that provides the `Http\Message\StreamFactory`
5252
| httplug.client.[name] | This is your Httpclient that you have configured. With the configuration below the name would be `acme_client`.
5353
| httplug.client | This is the first client configured or a client named `default`.
54-
| httplug.plugin.content_length <br> httplug.plugin.decoder<br> httplug.plugin.error<br> httplug.plugin.logger<br> httplug.plugin.redirect<br> httplug.plugin.retry | These are build in plugins that lives in the `php-http/plugins` package. These servcies are not public and may only be used when configure HttpClients or services.
54+
| httplug.plugin.content_length <br> httplug.plugin.decoder<br> httplug.plugin.error<br> httplug.plugin.logger<br> httplug.plugin.redirect<br> httplug.plugin.retry | These are built in plugins that live in the `php-http/plugins` package. These servcies are not public and may only be used when configure HttpClients or services.
5555

5656
\* *These services are always an alias to another service. You can specify your own service or leave the default, which is the same name with `.default` appended. The default services in turn use the service discovery mechanism to provide the best available implementation. You can specify a class for each of the default services to use instead of discovery, as long as those classes can be instantiated without arguments.*
5757

58+
5859
If you need a more custom setup, define the services in your application configuration and specify your service in the `main_alias` section. For example, to add authentication headers, you could define a service that decorates the service `httplug.client.default` with a plugin that injects the authentication headers into the request and configure `httplug.main_alias.client` to the name of your service.
5960

6061
```yaml

Resources/config/data-collector.xml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5+
6+
<services>
7+
<service id="httplug.collector.message_journal" class="Http\HttplugBundle\Collector\MessageJournal" public="false">
8+
<tag name="data_collector" template="HttplugBundle::webprofiler.html.twig" priority="200"
9+
id="httplug"/>
10+
<argument>null</argument>
11+
</service>
12+
13+
<service id="httplug.collector.history_plugin" class="Http\Client\Plugin\HistoryPlugin" public="false">
14+
<argument type="service" id="httplug.collector.message_journal"/>
15+
</service>
16+
</services>
17+
</container>

Resources/config/plugins.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
<services>
77
<service id="httplug.plugin.content_length" class="Http\Client\Plugin\ContentLengthPlugin" public="false" />
8-
<service id="httplug.plugin.decoder" class="Http\Client\Plugin\DecoderPlugin" public="false">
9-
</service>
8+
<service id="httplug.plugin.decoder" class="Http\Client\Plugin\DecoderPlugin" public="false" />
109
<service id="httplug.plugin.error" class="Http\Client\Plugin\ErrorPlugin" public="false" />
1110
<service id="httplug.plugin.logger" class="Http\Client\Plugin\LoggerPlugin" public="false">
1211
<argument type="service" id="logger"/>

Resources/views/webprofiler.html.twig

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% import _self as macro %}
4+
5+
{% block toolbar %}
6+
{% if collector.totalRequests > 0 %}
7+
{% set icon %}
8+
{{ include('@WebProfiler/Icon/ajax.svg') }}
9+
<span class="sf-toolbar-status">{{ collector.totalRequests }}</span>
10+
{% endset %}
11+
12+
{% set text %}
13+
<div class="sf-toolbar-info-piece">
14+
<b>Successful requests</b>
15+
<span>{{ collector.sucessfulRequests|length }}</span>
16+
</div>
17+
<div class="sf-toolbar-info-piece">
18+
<b>Faild requests</b>
19+
<span>{{ collector.failedRequests|length }}</span>
20+
</div>
21+
22+
{% endset %}
23+
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
24+
{% endif %}
25+
{% endblock %}
26+
27+
{% block head %}
28+
{# Optional. Here you can link to or define your own CSS and JS contents. #}
29+
{{ parent() }}
30+
{% endblock %}
31+
32+
{% block menu %}
33+
{# This left-hand menu appears when using the full-screen profiler. #}
34+
<span class="label {{ collector.totalRequests == 0 ? 'disabled' }}">
35+
<span class="icon">{{ include('@WebProfiler/Icon/ajax.svg') }}</span>
36+
<strong>Httplug</strong>
37+
</span>
38+
{% endblock %}
39+
40+
{% block panel %}
41+
<h2>HTTPlug</h2>
42+
{% if (collector.failedRequests|length > 0) %}
43+
<h3>Failed requests</h3>
44+
{{ macro.printMessages(collector.failedRequests) }}
45+
{% endif %}
46+
47+
{% if (collector.sucessfulRequests|length > 0) %}
48+
<h3>Successful requests</h3>
49+
{{ macro.printMessages(collector.sucessfulRequests) }}
50+
{% endif %}
51+
52+
{% if collector.totalRequests == 0 %}
53+
54+
<div class="empty">
55+
<p>No request were sent.</p>
56+
</div>
57+
{% endif %}
58+
59+
{% endblock %}
60+
61+
{% macro printMessages(messages) %}
62+
<table>
63+
<tr>
64+
<th>Request</th>
65+
<th>Response</th>
66+
</tr>
67+
68+
{% for message in messages %}
69+
<tr>
70+
<td>{{ message['request'] }}</td>
71+
<td>{{ message['response'] }}</td>
72+
</tr>
73+
{% endfor %}
74+
</table>
75+
{% endmacro %}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<?php
22

3-
$container->loadFromExtension('httplug', array());
3+
$container->loadFromExtension('httplug', []);
+12-12
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
<?php
22

3-
$container->loadFromExtension('httplug', array(
4-
'main_alias' => array(
5-
'client' => 'my_client',
3+
$container->loadFromExtension('httplug', [
4+
'main_alias' => [
5+
'client' => 'my_client',
66
'message_factory' => 'my_message_factory',
7-
'uri_factory' => 'my_uri_factory',
8-
'stream_factory' => 'my_stream_factory',
9-
),
10-
'classes' => array(
11-
'client' => 'Http\Adapter\Guzzle6\Client',
7+
'uri_factory' => 'my_uri_factory',
8+
'stream_factory' => 'my_stream_factory',
9+
],
10+
'classes' => [
11+
'client' => 'Http\Adapter\Guzzle6\Client',
1212
'message_factory' => 'Http\Message\MessageFactory\GuzzleMessageFactory',
13-
'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory',
14-
'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory',
15-
),
16-
));
13+
'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory',
14+
'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory',
15+
],
16+
]);

Tests/Resources/app/AppKernel.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ class AppKernel extends Kernel
1010
*/
1111
public function registerBundles()
1212
{
13-
return array(
13+
return [
1414
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
1515
new \Http\HttplugBundle\HttplugBundle(),
16-
);
16+
];
1717
}
1818

1919
/**

Tests/Unit/DependencyInjection/ConfigurationTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public function testEmptyConfiguration()
3737
'stream_factory' => null,
3838
],
3939
'clients' => [],
40+
'toolbar' => [
41+
'enabled' => 'auto',
42+
'formatter' => null,
43+
],
4044
];
4145

4246
$formats = array_map(function ($path) {
@@ -68,6 +72,10 @@ public function testSupportsAllConfigFormats()
6872
'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory',
6973
],
7074
'clients' => [],
75+
'toolbar' => [
76+
'enabled' => 'auto',
77+
'formatter' => null,
78+
],
7179
];
7280

7381
$formats = array_map(function ($path) {

0 commit comments

Comments
 (0)