Skip to content

Commit

Permalink
[Provider] Create ProviderRegistry service (BC break)
Browse files Browse the repository at this point in the history
This introduces a registry service for persistence providers.

Previously, tagging one or more provider services would cause AddProviderPass to clobber the default providers created by the bundle's extension class. Now, the extension class tags its created providers and allows them to be registered via RegisterProvidersPass just like custom providers.

BC break: Custom providers tagged "foq_elastica.provider" must now specify a "type" attribute on their tag. An "index" attribute is optional (the default ES index will be used by default).
  • Loading branch information
jmikola committed Mar 12, 2012
1 parent 43eda48 commit b360a36
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 32 deletions.
27 changes: 0 additions & 27 deletions DependencyInjection/Compiler/AddProviderPass.php

This file was deleted.

70 changes: 70 additions & 0 deletions DependencyInjection/Compiler/RegisterProvidersPass.php
@@ -0,0 +1,70 @@
<?php

namespace FOQ\ElasticaBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class RegisterProvidersPass implements CompilerPassInterface
{
/**
* Mapping of class names to booleans indicating whether the class
* implements ProviderInterface.
*
* @var array
*/
private $implementations = array();

/**
* @see Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::process()
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('foq_elastica.provider_registry')) {
return;
}

// Infer the default index name from the service alias
$defaultIndex = substr($container->getAlias('foq_elastica.index'), 19);

$registry = $container->getDefinition('foq_elastica.provider_registry');
$providers = $container->findTaggedServiceIds('foq_elastica.provider');

foreach ($providers as $providerId => $tags) {
$index = $type = null;
$class = $container->getDefinition($providerId)->getClass();

if (!$class || !$this->isProviderImplementation($class)) {
throw new \InvalidArgumentException(sprintf('Elastica provider "%s" with class "%s" must implement ProviderInterface.', $providerId, $class));
}

foreach ($tags as $tag) {
if (!isset($tag['type'])) {
throw new \InvalidArgumentException(sprintf('Elastica provider "%s" must specify the "type" attribute.', $providerId));
}

$index = isset($tag['index']) ? $tag['index'] : $defaultIndex;
$type = $tag['type'];
}

$registry->addMethodCall('addProvider', array($index, $type, $providerId));
}
}

/**
* Returns whether the class implements ProviderInterface.
*
* @param string $class
* @return boolean
*/
private function isProviderImplementation($class)
{
if (!isset($this->implementations[$class])) {
$refl = new \ReflectionClass($class);
$this->implementations[$class] = $refl->implementsInterface('FOQ\ElasticaBundle\Provider\ProviderInterface');
}

return $this->implementations[$class];
}
}
4 changes: 2 additions & 2 deletions DependencyInjection/FOQElasticaExtension.php
Expand Up @@ -213,8 +213,7 @@ protected function loadTypePersistenceIntegration(array $typeConfig, ContainerBu
$objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId); $objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId);


if (isset($typeConfig['provider'])) { if (isset($typeConfig['provider'])) {
$providerId = $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName);
$container->getDefinition('foq_elastica.populator')->addMethodCall('addProvider', array($providerId, new Reference($providerId)));
} }
if (isset($typeConfig['finder'])) { if (isset($typeConfig['finder'])) {
$this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName); $this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName);
Expand Down Expand Up @@ -285,6 +284,7 @@ protected function loadTypeProvider(array $typeConfig, ContainerBuilder $contain
$abstractProviderId = sprintf('foq_elastica.provider.prototype.%s', $typeConfig['driver']); $abstractProviderId = sprintf('foq_elastica.provider.prototype.%s', $typeConfig['driver']);
$providerId = sprintf('foq_elastica.provider.%s.%s', $indexName, $typeName); $providerId = sprintf('foq_elastica.provider.%s.%s', $indexName, $typeName);
$providerDef = new DefinitionDecorator($abstractProviderId); $providerDef = new DefinitionDecorator($abstractProviderId);
$providerDef->addTag('foq_elastica.provider', array('index' => $indexName, 'type' => $typeName));
$providerDef->replaceArgument(0, $typeDef); $providerDef->replaceArgument(0, $typeDef);


// Doctrine has a mandatory service as second argument // Doctrine has a mandatory service as second argument
Expand Down
10 changes: 7 additions & 3 deletions FOQElasticaBundle.php
Expand Up @@ -2,18 +2,22 @@


namespace FOQ\ElasticaBundle; namespace FOQ\ElasticaBundle;


use FOQ\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\AddProviderPass;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;


class FOQElasticaBundle extends Bundle class FOQElasticaBundle extends Bundle
{ {
/**
* @see Symfony\Component\HttpKernel\Bundle\Bundle::build()
*/
public function build(ContainerBuilder $container) public function build(ContainerBuilder $container)
{ {
parent::build($container); parent::build($container);


$container->addCompilerPass(new AddProviderPass()); $container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TransformerPass()); $container->addCompilerPass(new TransformerPass());
} }
} }
98 changes: 98 additions & 0 deletions Provider/ProviderRegistry.php
@@ -0,0 +1,98 @@
<?php

namespace FOQ\ElasticaBundle\Provider;

use Symfony\Component\DependencyInjection\ContainerBuilder;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* References persistence providers for each index and type.
*/
class ProviderRegistry implements ContainerAwareInterface
{
private $container;
private $providers = array();

/**
* Registers a provider for the specified index and type.
*
* @param string $index
* @param string $type
* @param string $providerId
*/
public function addProvider($index, $type, $providerId)
{
if (!isset($this->providers[$index])) {
$this->providers[$index] = array();
}

$this->providers[$index][$type] = $providerId;
}

/**
* Gets all registered providers.
*
* @return array of ProviderInterface instances
*/
public function getAllProviders()
{
$providers = array();

foreach ($this->providers as $indexProviders) {
foreach ($indexProviders as $providerId) {
$providers[] = $this->container->get($providerId);
}
}

return $providers;
}

/**
* Gets all providers for an index.
*
* @param string $index
* @return array of ProviderInterface instances
* @throws InvalidArgumentException if no providers were registered for the index
*/
public function getIndexProviders($index)
{
if (!isset($this->providers[$index])) {
throw new \InvalidArgumentException(sprintf('No providers were registered for index "%s".', $index));
}

$providers = array();

foreach ($this->providers[$index] as $providerId) {
$providers[] = $this->container->get($providerId);
}

return $providers;
}

/**
* Gets the provider for an index and type.
*
* @param string $index
* @param string $type
* @return ProviderInterface
* @throws InvalidArgumentException if no provider was registered for the index and type
*/
public function getProvider($index, $type)
{
if (!isset($this->providers[$index][$type])) {
throw new \InvalidArgumentException(sprintf('No provider was registered for index "%s" and type "%s".', $index, $type));
}

return $this->container->get($this->providers[$index][$type]);
}

/**
* @see Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}
6 changes: 6 additions & 0 deletions Resources/config/config.xml
Expand Up @@ -12,6 +12,7 @@
<parameter key="foq_elastica.data_collector.class">FOQ\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter> <parameter key="foq_elastica.data_collector.class">FOQ\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
<parameter key="foq_elastica.manager.class">FOQ\ElasticaBundle\Manager\RepositoryManager</parameter> <parameter key="foq_elastica.manager.class">FOQ\ElasticaBundle\Manager\RepositoryManager</parameter>
<parameter key="foq_elastica.elastica_to_model_transformer.collection.class">FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter> <parameter key="foq_elastica.elastica_to_model_transformer.collection.class">FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
<parameter key="foq_elastica.provider_registry.class">FOQ\ElasticaBundle\Provider\ProviderRegistry</parameter>
</parameters> </parameters>


<services> <services>
Expand Down Expand Up @@ -66,6 +67,11 @@
<argument type="collection" /> <!-- options --> <argument type="collection" /> <!-- options -->
</service> </service>


<service id="foq_elastica.provider_registry" class="%foq_elastica.provider_registry.class%">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
</services> </services>


</container> </container>
78 changes: 78 additions & 0 deletions Tests/DependencyInjection/Compiler/RegisterProvidersPassTest.php
@@ -0,0 +1,78 @@
<?php

namespace FOQ\ElasticaBundle\Tests\DependencyInjection\Compiler;

use FOQ\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class RegisterProvidersPassTest extends \PHPUnit_Framework_TestCase
{
public function testProcessShouldRegisterTaggedProviders()
{
$container = new ContainerBuilder();
$pass = new RegisterProvidersPass();

$registryDefinition = new Definition();

$container->setDefinition('foq_elastica.provider_registry', $registryDefinition);
$container->setAlias('foq_elastica.index', 'foq_elastica.index.foo');

$container->setDefinition('provider.foo.a', $this->createProviderDefinition(array('type' => 'a')));
$container->setDefinition('provider.foo.b', $this->createProviderDefinition(array('index' => 'foo', 'type' => 'b')));
$container->setDefinition('provider.bar.a', $this->createProviderDefinition(array('index' => 'bar', 'type' => 'a')));

$pass->process($container);

$calls = $registryDefinition->getMethodCalls();

$this->assertEquals(array('addProvider', array('foo', 'a', 'provider.foo.a')), $calls[0]);
$this->assertEquals(array('addProvider', array('foo', 'b', 'provider.foo.b')), $calls[1]);
$this->assertEquals(array('addProvider', array('bar', 'a', 'provider.bar.a')), $calls[2]);
}

/**
* @expectedException InvalidArgumentException
*/
public function testProcessShouldRequireProviderImplementation()
{
$container = new ContainerBuilder();
$pass = new RegisterProvidersPass();

$container->setDefinition('foq_elastica.provider_registry', new Definition());
$container->setAlias('foq_elastica.index', 'foq_elastica.index.foo');

$providerDef = $this->createProviderDefinition();
$providerDef->setClass('stdClass');

$container->setDefinition('provider.foo.a', $providerDef);

$pass->process($container);
}

/**
* @expectedException InvalidArgumentException
*/
public function testProcessShouldRequireTypeAttribute()
{
$container = new ContainerBuilder();
$pass = new RegisterProvidersPass();

$container->setDefinition('foq_elastica.provider_registry', new Definition());
$container->setAlias('foq_elastica.index', 'foq_elastica.index.foo');

$container->setDefinition('provider.foo.a', $this->createProviderDefinition());

$pass->process($container);
}

private function createProviderDefinition(array $attributes = array())
{
$provider = $this->getMock('FOQ\ElasticaBundle\Provider\ProviderInterface');

$definition = new Definition(get_class($provider));
$definition->addTag('foq_elastica.provider', $attributes);

return $definition;
}
}

0 comments on commit b360a36

Please sign in to comment.