diff --git a/Collector/MessageJournal.php b/Collector/MessageJournal.php new file mode 100644 index 00000000..9f87bece --- /dev/null +++ b/Collector/MessageJournal.php @@ -0,0 +1,109 @@ + + */ +class MessageJournal extends DataCollector implements Journal +{ + /** + * @var Formatter + */ + private $formatter; + + /** + * @param Formatter $formatter + */ + public function __construct(Formatter $formatter = null) + { + $this->formatter = $formatter ?: new SimpleFormatter(); + $this->data = ['success' => [], 'failure' => []]; + } + + /** + * {@inheritdoc} + */ + public function addSuccess(RequestInterface $request, ResponseInterface $response) + { + $this->data['success'][] = [ + 'request' => $this->formatter->formatRequest($request), + 'response' => $this->formatter->formatResponse($response), + ]; + } + + /** + * {@inheritdoc} + */ + public function addFailure(RequestInterface $request, Exception $exception) + { + if ($exception instanceof Exception\HttpException) { + $formattedResponse = $this->formatter->formatResponse($exception->getResponse()); + } elseif ($exception instanceof Exception\TransferException) { + $formattedResponse = $exception->getMessage(); + } else { + $formattedResponse = sprintf('Unexpected exception of type "%s"', get_class($exception)); + } + + $this->data['failure'][] = [ + 'request' => $this->formatter->formatRequest($request), + 'response' => $formattedResponse, + ]; + } + + /** + * Get the successful request-resonse pairs. + * + * @return array + */ + public function getSucessfulRequests() + { + return $this->data['success']; + } + + /** + * Get the failed request-resonse pairs. + * + * @return array + */ + public function getFailedRequests() + { + return $this->data['failure']; + } + + /** + * Get the total number of request made. + * + * @return int + */ + public function getTotalRequests() + { + return count($this->data['success']) + count($this->data['failure']); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // We do not need to collect any data form the Symfony Request and Response + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'httplug'; + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index c3684312..9347979f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -34,8 +34,7 @@ public function getConfigTreeBuilder() return !empty($v['classes']['client']) || !empty($v['classes']['message_factory']) || !empty($v['classes']['uri_factory']) - || !empty($v['classes']['stream_factory']) - ; + || !empty($v['classes']['stream_factory']); }) ->then(function ($v) { foreach ($v['classes'] as $key => $class) { @@ -72,8 +71,19 @@ public function getConfigTreeBuilder() ->scalarNode('stream_factory')->defaultNull()->end() ->end() ->end() - ->end() - ; + ->arrayNode('toolbar') + ->addDefaultsIfNotSet() + ->info('Extend the debug profiler with inforation about requests.') + ->children() + ->enumNode('enabled') + ->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.') + ->values([true, false, 'auto']) + ->defaultValue('auto') + ->end() + ->scalarNode('formatter')->defaultNull()->end() + ->end() + ->end() + ->end(); return $treeBuilder; } diff --git a/DependencyInjection/HttplugExtension.php b/DependencyInjection/HttplugExtension.php index f30e0bfd..906a580c 100644 --- a/DependencyInjection/HttplugExtension.php +++ b/DependencyInjection/HttplugExtension.php @@ -3,8 +3,8 @@ namespace Http\HttplugBundle\DependencyInjection; use Http\HttplugBundle\ClientFactory\DummyClient; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -27,6 +27,18 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('services.xml'); $loader->load('plugins.xml'); $loader->load('discovery.xml'); + + $enabled = is_bool($config['toolbar']['enabled']) ? $config['toolbar']['enabled'] : $container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug'); + if ($enabled) { + $loader->load('data-collector.xml'); + $config['_inject_collector_plugin'] = true; + + if (!empty($config['toolbar']['formatter'])) { + $container->getDefinition('httplug.collector.message_journal') + ->replaceArgument(0, new Reference($config['toolbar']['formatter'])); + } + } + foreach ($config['classes'] as $service => $class) { if (!empty($class)) { $container->removeDefinition(sprintf('httplug.%s.default', $service)); @@ -54,6 +66,10 @@ protected function configureClients(ContainerBuilder $container, array $config) $first = $name; } + if (isset($config['_inject_collector_plugin'])) { + array_unshift($arguments['plugins'], 'httplug.collector.history_plugin'); + } + $def = $container->register('httplug.client.'.$name, DummyClient::class); if (empty($arguments['plugins'])) { diff --git a/README.md b/README.md index 65e21cc0..cb8134a7 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,11 @@ For information how to write applications with the services provided by this bun | httplug.stream_factory | Service* that provides the `Http\Message\StreamFactory` | httplug.client.[name] | This is your Httpclient that you have configured. With the configuration below the name would be `acme_client`. | httplug.client | This is the first client configured or a client named `default`. -| httplug.plugin.content_length
httplug.plugin.decoder
httplug.plugin.error
httplug.plugin.logger
httplug.plugin.redirect
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. +| httplug.plugin.content_length
httplug.plugin.decoder
httplug.plugin.error
httplug.plugin.logger
httplug.plugin.redirect
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. \* *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.* + 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. ```yaml diff --git a/Resources/config/data-collector.xml b/Resources/config/data-collector.xml new file mode 100644 index 00000000..680e806d --- /dev/null +++ b/Resources/config/data-collector.xml @@ -0,0 +1,17 @@ + + + + + + + null + + + + + + + diff --git a/Resources/config/plugins.xml b/Resources/config/plugins.xml index 72f27834..a23f4a31 100644 --- a/Resources/config/plugins.xml +++ b/Resources/config/plugins.xml @@ -5,8 +5,7 @@ - - + diff --git a/Resources/views/webprofiler.html.twig b/Resources/views/webprofiler.html.twig new file mode 100644 index 00000000..93c70996 --- /dev/null +++ b/Resources/views/webprofiler.html.twig @@ -0,0 +1,75 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as macro %} + +{% block toolbar %} + {% if collector.totalRequests > 0 %} + {% set icon %} + {{ include('@WebProfiler/Icon/ajax.svg') }} + {{ collector.totalRequests }} + {% endset %} + + {% set text %} +
+ Successful requests + {{ collector.sucessfulRequests|length }} +
+
+ Faild requests + {{ collector.failedRequests|length }} +
+ + {% endset %} + {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %} + {% endif %} +{% endblock %} + +{% block head %} + {# Optional. Here you can link to or define your own CSS and JS contents. #} + {{ parent() }} +{% endblock %} + +{% block menu %} + {# This left-hand menu appears when using the full-screen profiler. #} + + {{ include('@WebProfiler/Icon/ajax.svg') }} + Httplug + +{% endblock %} + +{% block panel %} +

HTTPlug

+ {% if (collector.failedRequests|length > 0) %} +

Failed requests

+ {{ macro.printMessages(collector.failedRequests) }} + {% endif %} + + {% if (collector.sucessfulRequests|length > 0) %} +

Successful requests

+ {{ macro.printMessages(collector.sucessfulRequests) }} + {% endif %} + + {% if collector.totalRequests == 0 %} + +
+

No request were sent.

+
+ {% endif %} + +{% endblock %} + +{% macro printMessages(messages) %} + + + + + + + {% for message in messages %} + + + + + {% endfor %} +
RequestResponse
{{ message['request'] }}{{ message['response'] }}
+{% endmacro %} diff --git a/Tests/Resources/Fixtures/config/empty.php b/Tests/Resources/Fixtures/config/empty.php index d599ed25..02c22151 100644 --- a/Tests/Resources/Fixtures/config/empty.php +++ b/Tests/Resources/Fixtures/config/empty.php @@ -1,3 +1,3 @@ loadFromExtension('httplug', array()); +$container->loadFromExtension('httplug', []); diff --git a/Tests/Resources/Fixtures/config/full.php b/Tests/Resources/Fixtures/config/full.php index 66aba790..2883da4d 100644 --- a/Tests/Resources/Fixtures/config/full.php +++ b/Tests/Resources/Fixtures/config/full.php @@ -1,16 +1,16 @@ loadFromExtension('httplug', array( - 'main_alias' => array( - 'client' => 'my_client', +$container->loadFromExtension('httplug', [ + 'main_alias' => [ + 'client' => 'my_client', 'message_factory' => 'my_message_factory', - 'uri_factory' => 'my_uri_factory', - 'stream_factory' => 'my_stream_factory', - ), - 'classes' => array( - 'client' => 'Http\Adapter\Guzzle6\Client', + 'uri_factory' => 'my_uri_factory', + 'stream_factory' => 'my_stream_factory', + ], + 'classes' => [ + 'client' => 'Http\Adapter\Guzzle6\Client', 'message_factory' => 'Http\Message\MessageFactory\GuzzleMessageFactory', - 'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory', - 'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory', - ), -)); + 'uri_factory' => 'Http\Message\UriFactory\GuzzleUriFactory', + 'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory', + ], +]); diff --git a/Tests/Resources/app/AppKernel.php b/Tests/Resources/app/AppKernel.php index 1d821d12..a6fb0780 100644 --- a/Tests/Resources/app/AppKernel.php +++ b/Tests/Resources/app/AppKernel.php @@ -10,10 +10,10 @@ class AppKernel extends Kernel */ public function registerBundles() { - return array( + return [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Http\HttplugBundle\HttplugBundle(), - ); + ]; } /** diff --git a/Tests/Unit/DependencyInjection/ConfigurationTest.php b/Tests/Unit/DependencyInjection/ConfigurationTest.php index 0c7d4523..84a64eac 100644 --- a/Tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/Tests/Unit/DependencyInjection/ConfigurationTest.php @@ -37,6 +37,10 @@ public function testEmptyConfiguration() 'stream_factory' => null, ], 'clients' => [], + 'toolbar' => [ + 'enabled' => 'auto', + 'formatter' => null, + ], ]; $formats = array_map(function ($path) { @@ -68,6 +72,10 @@ public function testSupportsAllConfigFormats() 'stream_factory' => 'Http\Message\StreamFactory\GuzzleStreamFactory', ], 'clients' => [], + 'toolbar' => [ + 'enabled' => 'auto', + 'formatter' => null, + ], ]; $formats = array_map(function ($path) {