Skip to content

Commit f710b9f

Browse files
fbourigaultNyholm
authored andcommitted
Add PluginClientFactory support (#202)
* add PluginClientFactory support * deprecate ClientFactory\PluginClientFactory * add PluginClientFactory and PluginClientFactorySubscriber class comments * improve ClientFactory\PluginClientFactory deprecation * fix typos * use client_name option for configured clients * update composer.json * fix composer.json * remove prefer-stable when testing against dev dependencies * add compatibility with old KernelTestCase::bootKernel signature * require symfony/dependency injection ^2.8.3 || ^3.0.3 * update the changelog * rename PluginClientFactorySubscriber to PluginClientFactoryListener
1 parent 2170560 commit f710b9f

File tree

11 files changed

+224
-82
lines changed

11 files changed

+224
-82
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ matrix:
3636

3737
before_install:
3838
- if [ "$SYMFONY_VERSION" != "" ]; then composer require "symfony/symfony:${SYMFONY_VERSION}" --no-update; fi;
39+
- if [ "$DEPENDENCIES" = "dev" ]; then sed -i '/prefer-stable/d' composer.json; fi;
3940
- if [ "$DEPENDENCIES" = "dev" ]; then perl -pi -e 's/^}$/,"minimum-stability":"dev"}/' composer.json; fi;
4041
- if [ $COVERAGE != true ]; then phpenv config-rm xdebug.ini; fi;
4142

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
44

5+
## Unreleased
6+
7+
### Added
8+
9+
- Any third party library using `Http\Client\Common\PluginClientFactory` to create `Http\Client\Common\PluginClient`
10+
instances now gets zero config profiling.
11+
- `Http\Client\Common\PluginClientFactory` factory service.
12+
13+
### Changed
14+
15+
- `ProfilePlugin` and `StackPlugin` are no longer registered as (private) services decorators. Those decorators are now
16+
created through the `Http\HttplugBundle\Collector\PluginClientFactory`.
17+
18+
### Deprecated
19+
20+
- The `Http\HttplugBundle\ClientFactory\PluginClientFactory` class.
21+
522
## 1.7.1 - 2017-08-04
623

724
### Fixed

ClientFactory/PluginClientFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
namespace Http\HttplugBundle\ClientFactory;
44

5+
@trigger_error('The '.__NAMESPACE__.'\PluginClientFactory class is deprecated since version 1.8 and will be removed in 2.0. Use Http\Client\Common\PluginClientFactory instead.', E_USER_DEPRECATED);
6+
57
use Http\Client\Common\Plugin;
68
use Http\Client\Common\PluginClient;
79

810
/**
911
* This factory creates a PluginClient.
1012
*
13+
* @deprecated
14+
*
1115
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
1216
*/
1317
final class PluginClientFactory

Collector/PluginClientFactory.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\Collector;
4+
5+
use Http\Client\Common\Plugin;
6+
use Http\Client\Common\PluginClient;
7+
use Http\Client\HttpAsyncClient;
8+
use Http\Client\HttpClient;
9+
use Symfony\Component\Stopwatch\Stopwatch;
10+
11+
/**
12+
* This factory is used as a replacement for Http\Client\Common\PluginClientFactory when profiling is enabled. It
13+
* creates PluginClient instances with all profiling decorators and extra plugins.
14+
*
15+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
16+
*
17+
* @internal
18+
*/
19+
final class PluginClientFactory
20+
{
21+
/**
22+
* @var Collector
23+
*/
24+
private $collector;
25+
26+
/**
27+
* @var Formatter
28+
*/
29+
private $formatter;
30+
31+
/**
32+
* @var Stopwatch
33+
*/
34+
private $stopwatch;
35+
36+
/**
37+
* @param Collector $collector
38+
* @param Formatter $formatter
39+
* @param Stopwatch $stopwatch
40+
*/
41+
public function __construct(Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
42+
{
43+
$this->collector = $collector;
44+
$this->formatter = $formatter;
45+
$this->stopwatch = $stopwatch;
46+
}
47+
48+
/**
49+
* @param HttpClient|HttpAsyncClient $client
50+
* @param Plugin[] $plugins
51+
* @param array $options {
52+
*
53+
* @var string $client_name to give client a name which may be used when displaying client information like in
54+
* the HTTPlugBundle profiler.
55+
* }
56+
*
57+
* @see PluginClient constructor for PluginClient specific $options.
58+
*
59+
* @return PluginClient
60+
*/
61+
public function createClient($client, array $plugins = [], array $options = [])
62+
{
63+
$plugins = array_map(function (Plugin $plugin) {
64+
return new ProfilePlugin($plugin, $this->collector, $this->formatter);
65+
}, $plugins);
66+
67+
$clientName = isset($options['client_name']) ? $options['client_name'] : 'Default';
68+
array_unshift($plugins, new StackPlugin($this->collector, $this->formatter, $clientName));
69+
unset($options['client_name']);
70+
71+
if (!$client instanceof ProfileClient) {
72+
$client = new ProfileClient($client, $this->collector, $this->formatter, $this->stopwatch);
73+
}
74+
75+
return new PluginClient($client, $plugins, $options);
76+
}
77+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\Collector;
4+
5+
use Http\Client\Common\PluginClientFactory;
6+
use Http\HttplugBundle\Collector\PluginClientFactory as CollectorPluginClientFactory;
7+
use Symfony\Component\EventDispatcher\Event;
8+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9+
10+
/**
11+
* This subscriber ensures that every PluginClient created when using Http\Client\Common\PluginClientFactory without
12+
* using the Symfony dependency injection container uses the Http\HttplugBundle\Collector\PluginClientFactory factory
13+
* when profiling is enabled. This allows 0 config profiling of third party libraries which use HTTPlug.
14+
*
15+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
16+
*
17+
* @internal
18+
*/
19+
final class PluginClientFactoryListener implements EventSubscriberInterface
20+
{
21+
/**
22+
* @var CollectorPluginClientFactory
23+
*/
24+
private $factory;
25+
26+
/**
27+
* @param CollectorPluginClientFactory $factory
28+
*/
29+
public function __construct(CollectorPluginClientFactory $factory)
30+
{
31+
$this->factory = $factory;
32+
}
33+
34+
/**
35+
* Make sure to profile clients created using PluginClientFactory.
36+
*
37+
* @param Event $e
38+
*/
39+
public function onEvent(Event $e)
40+
{
41+
PluginClientFactory::setFactory([$this->factory, 'createClient']);
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public static function getSubscribedEvents()
48+
{
49+
return [
50+
'kernel.request' => ['onEvent', 1024],
51+
'console.command' => ['onEvent', 1024],
52+
];
53+
}
54+
}

DependencyInjection/HttplugExtension.php

Lines changed: 17 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
use Http\Client\Common\HttpMethodsClient;
88
use Http\Client\Common\Plugin\AuthenticationPlugin;
99
use Http\Client\Common\PluginClient;
10-
use Http\HttplugBundle\ClientFactory\PluginClientFactory;
11-
use Http\HttplugBundle\Collector\ProfilePlugin;
10+
use Http\Client\Common\PluginClientFactory;
11+
use Http\Client\HttpClient;
1212
use Http\Message\Authentication\BasicAuth;
1313
use Http\Message\Authentication\Bearer;
1414
use Http\Message\Authentication\Wsse;
@@ -72,7 +72,7 @@ public function load(array $configs, ContainerBuilder $container)
7272
;
7373
}
7474

75-
$this->configureClients($container, $config, $profilingEnabled);
75+
$this->configureClients($container, $config);
7676
$this->configurePlugins($container, $config['plugins']); // must be after clients, as clients.X.plugins might use plugins as templates that will be removed
7777
$this->configureAutoDiscoveryClients($container, $config);
7878
}
@@ -82,9 +82,8 @@ public function load(array $configs, ContainerBuilder $container)
8282
*
8383
* @param ContainerBuilder $container
8484
* @param array $config
85-
* @param bool $profiling
8685
*/
87-
private function configureClients(ContainerBuilder $container, array $config, $profiling)
86+
private function configureClients(ContainerBuilder $container, array $config)
8887
{
8988
$first = null;
9089
$clients = [];
@@ -95,7 +94,7 @@ private function configureClients(ContainerBuilder $container, array $config, $p
9594
$first = $name;
9695
}
9796

98-
$this->configureClient($container, $name, $arguments, $this->isConfigEnabled($container, $config['profiling']));
97+
$this->configureClient($container, $name, $arguments);
9998
$clients[] = $name;
10099
}
101100

@@ -266,9 +265,8 @@ private function configureAuthentication(ContainerBuilder $container, array $con
266265
* @param ContainerBuilder $container
267266
* @param string $clientName
268267
* @param array $arguments
269-
* @param bool $profiling
270268
*/
271-
private function configureClient(ContainerBuilder $container, $clientName, array $arguments, $profiling)
269+
private function configureClient(ContainerBuilder $container, $clientName, array $arguments)
272270
{
273271
$serviceId = 'httplug.client.'.$clientName;
274272

@@ -285,23 +283,17 @@ private function configureClient(ContainerBuilder $container, $clientName, array
285283
}
286284
}
287285

288-
$pluginClientOptions = [];
289-
if ($profiling) {
290-
//Decorate each plugin with a ProfilePlugin instance.
291-
$plugins = array_map(function ($pluginServiceId) use ($container) {
292-
$this->decoratePluginWithProfilePlugin($container, $pluginServiceId);
293-
294-
return $pluginServiceId.'.debug';
295-
}, $plugins);
296-
297-
// To profile the requests, add a StackPlugin as first plugin in the chain.
298-
$stackPluginId = $this->configureStackPlugin($container, $clientName, $serviceId);
299-
array_unshift($plugins, $stackPluginId);
300-
}
286+
$container
287+
->register($serviceId.'.client', HttpClient::class)
288+
->setFactory([new Reference($arguments['factory']), 'createClient'])
289+
->addArgument($arguments['config'])
290+
->setPublic(false)
291+
;
301292

302293
$container
303294
->register($serviceId, PluginClient::class)
304-
->setFactory([PluginClientFactory::class, 'createPluginClient'])
295+
->setFactory([new Reference(PluginClientFactory::class), 'createClient'])
296+
->addArgument(new Reference($serviceId.'.client'))
305297
->addArgument(
306298
array_map(
307299
function ($id) {
@@ -310,9 +302,9 @@ function ($id) {
310302
$plugins
311303
)
312304
)
313-
->addArgument(new Reference($arguments['factory']))
314-
->addArgument($arguments['config'])
315-
->addArgument($pluginClientOptions)
305+
->addArgument([
306+
'client_name' => $clientName,
307+
])
316308
;
317309

318310
/*
@@ -432,44 +424,4 @@ private function configurePlugin(ContainerBuilder $container, $serviceId, $plugi
432424

433425
return $pluginServiceId;
434426
}
435-
436-
/**
437-
* Decorate the plugin service with a ProfilePlugin service.
438-
*
439-
* @param ContainerBuilder $container
440-
* @param string $pluginServiceId
441-
*/
442-
private function decoratePluginWithProfilePlugin(ContainerBuilder $container, $pluginServiceId)
443-
{
444-
$container->register($pluginServiceId.'.debug', ProfilePlugin::class)
445-
->setArguments([
446-
new Reference($pluginServiceId),
447-
new Reference('httplug.collector.collector'),
448-
new Reference('httplug.collector.formatter'),
449-
])
450-
->setPublic(false);
451-
}
452-
453-
/**
454-
* Configure a StackPlugin for a client.
455-
*
456-
* @param ContainerBuilder $container
457-
* @param string $clientName Client name to display in the profiler.
458-
* @param string $serviceId Client service id. Used as base for the StackPlugin service id.
459-
*
460-
* @return string configured StackPlugin service id
461-
*/
462-
private function configureStackPlugin(ContainerBuilder $container, $clientName, $serviceId)
463-
{
464-
$pluginServiceId = $serviceId.'.plugin.stack';
465-
466-
$definition = class_exists(ChildDefinition::class)
467-
? new ChildDefinition('httplug.plugin.stack')
468-
: new DefinitionDecorator('httplug.plugin.stack');
469-
470-
$definition->addArgument($clientName);
471-
$container->setDefinition($pluginServiceId, $definition);
472-
473-
return $pluginServiceId;
474-
}
475427
}

Resources/config/data-collector.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,17 @@
8484
<argument type="service" id="httplug.collector.formatter"/>
8585
<argument type="service" id="debug.stopwatch"/>
8686
</service>
87+
88+
<service id="Http\Client\Common\PluginClientFactory" class="Http\HttplugBundle\Collector\PluginClientFactory" public="false">
89+
<argument type="service" id="httplug.collector.collector"/>
90+
<argument type="service" id="httplug.collector.formatter"/>
91+
<argument type="service" id="debug.stopwatch"/>
92+
</service>
93+
94+
<service id="Http\HttplugBundle\Collector\PluginClientFactoryListener" class="Http\HttplugBundle\Collector\PluginClientFactoryListener">
95+
<argument type="service" id="Http\Client\Common\PluginClientFactory" />
96+
97+
<tag name="kernel.event_subscriber" />
98+
</service>
8799
</services>
88100
</container>

Resources/config/services.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
</service>
4545
<service id="Http\Client\HttpClient" alias="httplug.client" public="false" />
4646

47+
<!-- PluginClientFactory -->
48+
<service id="Http\Client\Common\PluginClientFactory" class="Http\Client\Common\PluginClientFactory" public="false" />
49+
4750
<!-- ClientFactories -->
4851
<service id="httplug.factory.auto" class="Http\HttplugBundle\ClientFactory\AutoDiscoveryFactory" public="false" />
4952
<service id="httplug.factory.buzz" class="Http\HttplugBundle\ClientFactory\BuzzFactory" public="false">

Tests/Functional/ServiceInstantiationTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
use Http\HttplugBundle\Collector\StackPlugin;
1212
use Nyholm\NSA;
1313
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
14+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
17+
use Symfony\Component\HttpKernel\HttpKernelInterface;
18+
use Symfony\Component\HttpKernel\KernelEvents;
1419
use Symfony\Component\HttpKernel\Profiler\Profiler;
1520

1621
class ServiceInstantiationTest extends WebTestCase
@@ -71,4 +76,21 @@ public function testProfilingDecoration()
7176
$this->assertInstanceOf(ProfilePlugin::class, $plugins[3]);
7277
$this->assertInstanceOf(ProfilePlugin::class, $plugins[4]);
7378
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
protected static function bootKernel(array $options = [])
84+
{
85+
parent::bootKernel($options);
86+
87+
/** @var EventDispatcherInterface $dispatcher */
88+
$dispatcher = static::$kernel->getContainer()->get('event_dispatcher');
89+
90+
$event = new GetResponseEvent(static::$kernel, new Request(), HttpKernelInterface::MASTER_REQUEST);
91+
92+
$dispatcher->dispatch(KernelEvents::REQUEST, $event);
93+
94+
return static::$kernel;
95+
}
7496
}

0 commit comments

Comments
 (0)