Skip to content

Commit 4463f6c

Browse files
committed
Remove discovery dependency, fixes #40, closes #54
POC how to not be dependent on discovery Fix unit tests Fix factory method Remove duplicated service Move discovery to compiler pass Applied fixes from StyleCI Fix puli executable Use class constant Fix puli command order Fix lowest dependency issue Fix HHVM tests Improve tests
1 parent 5eadbc6 commit 4463f6c

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 <mark.sagikazar@gmail.com>
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 <mark.sagikazar@gmail.com>
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 <mark.sagikazar@gmail.com>
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)