Skip to content

Commit 453e59c

Browse files
committed
Merge pull request #55 from php-http/httplug_factory
[WIP] Remove discovery dependency
2 parents c0fe282 + 4463f6c commit 453e59c

File tree

11 files changed

+291
-48
lines changed

11 files changed

+291
-48
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ composer.lock
55
phpspec.yml
66
phpunit.xml
77
puli.json
8+
puli.phar

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ before_install:
3434
- travis_retry composer self-update
3535

3636
install:
37+
- wget https://github.com/puli/cli/releases/download/1.0.0-beta10/puli.phar && chmod +x puli.phar
3738
- composer require symfony/symfony:${SYMFONY_VERSION} --no-update
3839
- travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction
3940

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\DependencyInjection\Compiler;
4+
5+
use Http\Client\HttpClient;
6+
use Http\HttplugBundle\HttplugFactory;
7+
use Http\Message\MessageFactory;
8+
use Http\Message\StreamFactory;
9+
use Http\Message\UriFactory;
10+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
13+
use Symfony\Component\DependencyInjection\Reference;
14+
15+
/**
16+
* Adds fallback and discovery services.
17+
*
18+
* @author Márk Sági-Kazár <[email protected]>
19+
*/
20+
final class DiscoveryPass implements CompilerPassInterface
21+
{
22+
/**
23+
* Fallback services and classes.
24+
*
25+
* @var array
26+
*/
27+
private $services = [
28+
'client' => HttpClient::class,
29+
'message_factory' => MessageFactory::class,
30+
'uri_factory' => UriFactory::class,
31+
'stream_factory' => StreamFactory::class,
32+
];
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function process(ContainerBuilder $container)
38+
{
39+
$useDiscovery = false;
40+
41+
foreach ($this->services as $service => $class) {
42+
$serviceId = sprintf('httplug.%s.default', $service);
43+
44+
if (false === $container->has($serviceId)) {
45+
// Register and create factory for the first time
46+
if (false === $useDiscovery) {
47+
$this->registerFactory($container);
48+
49+
$factory = [
50+
new Reference('httplug.factory'),
51+
'find',
52+
];
53+
54+
$useDiscovery = true;
55+
}
56+
57+
$definition = $container->register($serviceId, $class);
58+
59+
$definition->setFactory($factory);
60+
$definition->addArgument($class);
61+
}
62+
}
63+
}
64+
65+
/**
66+
* @param ContainerBuilder $container
67+
*
68+
* @throws RuntimeException
69+
*/
70+
private function registerFactory(ContainerBuilder $container)
71+
{
72+
if (false === $container->has('puli.discovery')) {
73+
throw new RuntimeException(
74+
'You need to install puli/symfony-bundle or add configuration at httplug.classes in order to use this bundle. Refer to http://docs.php-http/en/latest/integrations/index.html'
75+
);
76+
}
77+
78+
$definition = $container->register('httplug.factory', HttplugFactory::class);
79+
80+
$definition
81+
->addArgument(new Reference('puli.discovery'))
82+
->setPublic(false)
83+
;
84+
}
85+
}

DependencyInjection/HttplugExtension.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public function load(array $configs, ContainerBuilder $container)
3333

3434
$loader->load('services.xml');
3535
$loader->load('plugins.xml');
36-
$loader->load('discovery.xml');
3736

3837
$enabled = is_bool($config['toolbar']['enabled']) ? $config['toolbar']['enabled'] : $container->hasParameter('kernel.debug') && $container->getParameter('kernel.debug');
3938
if ($enabled) {
@@ -48,14 +47,14 @@ public function load(array $configs, ContainerBuilder $container)
4847

4948
foreach ($config['classes'] as $service => $class) {
5049
if (!empty($class)) {
51-
$container->removeDefinition(sprintf('httplug.%s.default', $service));
5250
$container->register(sprintf('httplug.%s.default', $service), $class);
5351
}
5452
}
5553

5654
foreach ($config['main_alias'] as $type => $id) {
5755
$container->setAlias(sprintf('httplug.%s', $type), $id);
5856
}
57+
5958
$this->configurePlugins($container, $config['plugins']);
6059
$this->configureClients($container, $config);
6160
}
@@ -174,7 +173,6 @@ private function configurePluginByName($name, Definition $definition, array $con
174173

175174
/**
176175
* @param ContainerBuilder $container
177-
* @param Definition $parent
178176
* @param array $config
179177
*/
180178
private function configureAuthentication(ContainerBuilder $container, array $config)

HttplugBundle.php

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

33
namespace Http\HttplugBundle;
44

5+
use Http\HttplugBundle\DependencyInjection\Compiler\DiscoveryPass;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
57
use Symfony\Component\HttpKernel\Bundle\Bundle;
68

79
/**
@@ -10,4 +12,10 @@
1012
*/
1113
class HttplugBundle extends Bundle
1214
{
15+
public function build(ContainerBuilder $container)
16+
{
17+
parent::build($container);
18+
19+
$container->addCompilerPass(new DiscoveryPass());
20+
}
1321
}

HttplugFactory.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle;
4+
5+
use Puli\Discovery\Api\Discovery;
6+
use Puli\Discovery\Binding\ClassBinding;
7+
use Webmozart\Expression\Expr;
8+
9+
/**
10+
* This class is a wrapper around Puli discovery.
11+
*
12+
* @author Márk Sági-Kazár <[email protected]>
13+
*/
14+
final class HttplugFactory
15+
{
16+
/**
17+
* @var Discovery
18+
*/
19+
private $discovery;
20+
21+
/**
22+
* @param Discovery $discovery
23+
*/
24+
public function __construct(Discovery $discovery)
25+
{
26+
$this->discovery = $discovery;
27+
}
28+
29+
/**
30+
* Creates a class of type.
31+
*
32+
* @param string $type
33+
*
34+
* @return object
35+
*/
36+
public function find($type)
37+
{
38+
$class = $this->findOneByType($type);
39+
40+
// TODO: use doctrine instantiator?
41+
return new $class();
42+
}
43+
44+
/**
45+
* Finds a class of type and resolves optional dependency conditions.
46+
*
47+
* @param string $type
48+
*
49+
* @return string
50+
*
51+
* @throws \RuntimeException if no class is found.
52+
*/
53+
private function findOneByType($type)
54+
{
55+
/** @var ClassBinding[] $bindings */
56+
$bindings = $this->discovery->findBindings(
57+
$type,
58+
Expr::isInstanceOf('Puli\Discovery\Binding\ClassBinding')
59+
);
60+
61+
foreach ($bindings as $binding) {
62+
if ($binding->hasParameterValue('depends')) {
63+
$dependency = $binding->getParameterValue('depends');
64+
65+
if (false === $this->evaluateCondition($dependency)) {
66+
continue;
67+
}
68+
}
69+
70+
return $binding->getClassName();
71+
}
72+
73+
throw new \RuntimeException(sprintf('Class binding of type "%s" is not found', $type));
74+
}
75+
76+
/**
77+
* Evaulates conditions to boolean.
78+
*
79+
* @param mixed $condition
80+
*
81+
* @return bool
82+
*
83+
* TODO: review this method
84+
*/
85+
protected function evaluateCondition($condition)
86+
{
87+
if (is_string($condition)) {
88+
// Should be extended for functions, extensions???
89+
return class_exists($condition);
90+
} elseif (is_callable($condition)) {
91+
return $condition();
92+
} elseif (is_bool($condition)) {
93+
return $condition;
94+
} elseif (is_array($condition)) {
95+
$evaluatedCondition = true;
96+
97+
// Immediately stop execution if the condition is false
98+
for ($i = 0; $i < count($condition) && false !== $evaluatedCondition; ++$i) {
99+
$evaluatedCondition &= $this->evaluateCondition($condition[$i]);
100+
}
101+
102+
return $evaluatedCondition;
103+
}
104+
105+
return false;
106+
}
107+
}

Resources/config/discovery.xml

Lines changed: 0 additions & 25 deletions
This file was deleted.

Tests/Resources/app/AppKernel.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ class AppKernel extends Kernel
1010
*/
1111
public function registerBundles()
1212
{
13-
return [
13+
$bundles = [
1414
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
1515
new \Http\HttplugBundle\HttplugBundle(),
1616
];
17+
18+
if (false === defined('HHVM_VERSION')) {
19+
$bundles[] = new \Puli\SymfonyBundle\PuliBundle();
20+
}
21+
22+
return $bundles;
1723
}
1824

1925
/**
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
namespace Http\HttplugBundle\Tests\Unit\DependencyInjection\Compiler;
4+
5+
use Http\Client\HttpClient;
6+
use Http\HttplugBundle\DependencyInjection\Compiler\DiscoveryPass;
7+
use Http\HttplugBundle\HttplugFactory;
8+
use Http\Message\MessageFactory;
9+
use Http\Message\StreamFactory;
10+
use Http\Message\UriFactory;
11+
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
12+
use Symfony\Component\DependencyInjection\ContainerBuilder;
13+
use Symfony\Component\DependencyInjection\Definition;
14+
15+
/**
16+
* @author Márk Sági-Kazár <[email protected]>
17+
*/
18+
final class DiscoveryPassTest extends AbstractCompilerPassTestCase
19+
{
20+
protected function registerCompilerPass(ContainerBuilder $container)
21+
{
22+
$container->addCompilerPass(new DiscoveryPass());
23+
}
24+
25+
public function testDiscoveryFallbacks()
26+
{
27+
$this->setDefinition('puli.discovery', new Definition('Puli\Discovery\Api\Discovery'));
28+
29+
$this->compile();
30+
31+
$this->assertContainerBuilderHasService('httplug.factory', HttplugFactory::class);
32+
33+
$this->assertContainerBuilderHasService('httplug.client.default', HttpClient::class);
34+
$this->assertContainerBuilderHasService('httplug.message_factory.default', MessageFactory::class);
35+
$this->assertContainerBuilderHasService('httplug.uri_factory.default', UriFactory::class);
36+
$this->assertContainerBuilderHasService('httplug.stream_factory.default', StreamFactory::class);
37+
}
38+
39+
public function testDiscoveryPartialFallbacks()
40+
{
41+
$this->setDefinition('puli.discovery', new Definition('Puli\Discovery\Api\Discovery'));
42+
$this->setDefinition('httplug.client.default', new Definition('Http\Adapter\Guzzle6\Client'));
43+
44+
$this->compile();
45+
46+
$this->assertContainerBuilderHasService('httplug.factory', HttplugFactory::class);
47+
48+
$this->assertContainerBuilderHasService('httplug.client.default', 'Http\Adapter\Guzzle6\Client');
49+
$this->assertContainerBuilderHasService('httplug.message_factory.default', MessageFactory::class);
50+
$this->assertContainerBuilderHasService('httplug.uri_factory.default', UriFactory::class);
51+
$this->assertContainerBuilderHasService('httplug.stream_factory.default', StreamFactory::class);
52+
}
53+
54+
public function testNoDiscoveryFallbacks()
55+
{
56+
$this->setDefinition('httplug.client.default', new Definition(HttpClient::class));
57+
$this->setDefinition('httplug.message_factory.default', new Definition(MessageFactory::class));
58+
$this->setDefinition('httplug.uri_factory.default', new Definition(UriFactory::class));
59+
$this->setDefinition('httplug.stream_factory.default', new Definition(StreamFactory::class));
60+
61+
$this->compile();
62+
63+
$this->assertContainerBuilderNotHasService('httplug.factory');
64+
}
65+
66+
/**
67+
* Overridden test as we have dependencies in this compiler pass.
68+
*
69+
* @test
70+
* @expectedException \RuntimeException
71+
*/
72+
public function compilation_should_not_fail_with_empty_container()
73+
{
74+
$this->compile();
75+
}
76+
}

0 commit comments

Comments
 (0)