diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php index 287fa7e..4ca41ad 100644 --- a/Command/PopulateCommand.php +++ b/Command/PopulateCommand.php @@ -15,33 +15,115 @@ use Symfony\Component\Console\Output\Output; class PopulateCommand extends ContainerAwareCommand { /** - * @see Command + * @var FOQ\ElasticaBundle\IndexManager + */ + private $indexManager; + + /** + * @var FOQ\ElasticaBundle\Provider\ProviderRegistry + */ + private $providerRegistry; + + /** + * @var FOQ\ElasticaBundle\Resetter + */ + private $resetter; + + /** + * @see Symfony\Component\Console\Command\Command::configure() */ protected function configure() { $this ->setName('foq:elastica:populate') - ->setDescription('Populates search indexes from providers'); + ->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to repopulate') + ->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type to repopulate') + ->setDescription('Populates search indexes from providers') + ; } /** - * {@inheritdoc} + * @see Symfony\Component\Console\Command\Command::initialize() + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + $this->indexManager = $this->getContainer()->get('foq_elastica.index_manager'); + $this->providerRegistry = $this->getContainer()->get('foq_elastica.provider_registry'); + $this->resetter = $this->getContainer()->get('foq_elastica.resetter'); + } + + /** + * @see Symfony\Component\Console\Command\Command::execute() */ protected function execute(InputInterface $input, OutputInterface $output) { - $output->writeln('Reseting indexes'); - $this->getContainer()->get('foq_elastica.reseter')->reset(); + $index = $input->getOption('index'); + $type = $input->getOption('type'); - $output->writeln('Populating indexes'); - $this->getContainer()->get('foq_elastica.populator')->populate(function($text) use ($output) { - $output->writeLn($text); - }); + if (null === $index && null !== $type) { + throw new \InvalidArgumentException('Cannot specify type option without an index.'); + } - $output->writeln('Refreshing indexes'); - array_map(function($index) { - $index->refresh(); - }, $this->getContainer()->get('foq_elastica.index_manager')->getAllIndexes()); + if (null !== $index) { + if (null !== $type) { + $this->populateIndexType($output, $index, $type); + } else { + $this->populateIndex($output, $index); + } + } else { + $indexes = array_keys($this->indexManager->getAllIndexes()); - $output->writeln('Done'); + foreach ($indexes as $index) { + $this->populateIndex($output, $index); + } + } + } + + /** + * Recreates an index, populates its types, and refreshes the index. + * + * @param OutputInterface $output + * @param string $index + */ + private function populateIndex(OutputInterface $output, $index) + { + $output->writeln(sprintf('Resetting: %s', $index)); + $this->resetter->resetIndex($index); + + $providers = $this->providerRegistry->getIndexProviders($index); + + foreach ($providers as $type => $provider) { + $loggerClosure = function($message) use ($output, $index, $type) { + $output->writeln(sprintf('Populating: %s/%s, %s', $index, $type, $message)); + }; + + $provider->populate($loggerClosure); + } + + $output->writeln(sprintf('Refreshing: %s', $index)); + $this->indexManager->getIndex($index)->refresh(); + } + + /** + * Deletes/remaps an index type, populates it, and refreshes the index. + * + * @param OutputInterface $output + * @param string $index + * @param string $type + */ + private function populateIndexType(OutputInterface $output, $index, $type) + { + $output->writeln(sprintf('Resetting: %s/%s', $index, $type)); + $this->resetter->resetIndexType($index, $type); + + $loggerClosure = function($message) use ($output, $index, $type) { + $output->writeln(sprintf('Populating: %s/%s, %s', $index, $type, $message)); + }; + + $provider = $this->providerRegistry->getProvider($index, $type); + $provider->populate($loggerClosure); + + $output->writeln(sprintf('Refreshing: %s', $index)); + $this->indexManager->getIndex($index)->refresh(); } } diff --git a/Command/SearchCommand.php b/Command/SearchCommand.php index 27b7e93..debd46b 100644 --- a/Command/SearchCommand.php +++ b/Command/SearchCommand.php @@ -22,18 +22,17 @@ class SearchCommand extends ContainerAwareCommand protected function configure() { $this - ->setDefinition(array( - new InputArgument('type', InputArgument::REQUIRED, 'The type to search in'), - new InputArgument('query', InputArgument::REQUIRED, 'The text to search'), - )) + ->setName('foq:elastica:search') + ->addArgument('type', InputArgument::REQUIRED, 'The type to search in') + ->addArgument('query', InputArgument::REQUIRED, 'The text to search') ->addOption('index', null, InputOption::VALUE_NONE, 'The index to search in') ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'The maximum number of documents to return', 20) ->addOption('show-field', null, InputOption::VALUE_REQUIRED, 'Field to show, null uses the first field') ->addOption('show-source', null, InputOption::VALUE_NONE, 'Show the documents sources') ->addOption('show-id', null, InputOption::VALUE_NONE, 'Show the documents ids') ->addOption('explain', null, InputOption::VALUE_NONE, 'Enables explanation for each hit on how its score was computed.') - ->setName('foq:elastica:search') - ->setDescription('Searches documents in a given type and index'); + ->setDescription('Searches documents in a given type and index') + ; } /** diff --git a/DependencyInjection/Compiler/AddProviderPass.php b/DependencyInjection/Compiler/AddProviderPass.php deleted file mode 100644 index bd9d15b..0000000 --- a/DependencyInjection/Compiler/AddProviderPass.php +++ /dev/null @@ -1,27 +0,0 @@ -hasDefinition('foq_elastica.populator')) { - return; - } - - $providers = array(); - foreach ($container->findTaggedServiceIds('foq_elastica.provider') as $id => $attributes) { - $providers[$id] = new Reference($id); - } - - $container->getDefinition('foq_elastica.populator')->replaceArgument(0, $providers); - } -} diff --git a/DependencyInjection/Compiler/RegisterProvidersPass.php b/DependencyInjection/Compiler/RegisterProvidersPass.php new file mode 100644 index 0000000..fc61e21 --- /dev/null +++ b/DependencyInjection/Compiler/RegisterProvidersPass.php @@ -0,0 +1,70 @@ +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]; + } +} diff --git a/DependencyInjection/FOQElasticaExtension.php b/DependencyInjection/FOQElasticaExtension.php index f4c0636..e28c478 100644 --- a/DependencyInjection/FOQElasticaExtension.php +++ b/DependencyInjection/FOQElasticaExtension.php @@ -43,12 +43,12 @@ class FOQElasticaExtension extends Extension $clientIdsByName = $this->loadClients($config['clients'], $container); $indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']); - $indexDefsByName = array_map(function($id) use ($container) { - return $container->getDefinition($id); + $indexRefsByName = array_map(function($id) { + return new Reference($id); }, $indexIdsByName); - $this->loadIndexManager($indexDefsByName, $container->getDefinition($indexIdsByName[$config['default_index']]), $container); - $this->loadReseter($this->indexConfigs, $container); + $this->loadIndexManager($indexRefsByName, $container); + $this->loadResetter($this->indexConfigs, $container); $container->setAlias('foq_elastica.client', sprintf('foq_elastica.client.%s', $config['default_client'])); $container->setAlias('foq_elastica.index', sprintf('foq_elastica.index.%s', $config['default_index'])); @@ -213,8 +213,7 @@ class FOQElasticaExtension extends Extension $objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId); if (isset($typeConfig['provider'])) { - $providerId = $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); - $container->getDefinition('foq_elastica.populator')->addMethodCall('addProvider', array($providerId, new Reference($providerId))); + $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); } if (isset($typeConfig['finder'])) { $this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName); @@ -282,25 +281,14 @@ class FOQElasticaExtension extends Extension if (isset($typeConfig['provider']['service'])) { return $typeConfig['provider']['service']; } - $abstractProviderId = sprintf('foq_elastica.provider.prototype.%s', $typeConfig['driver']); + $providerId = sprintf('foq_elastica.provider.%s.%s', $indexName, $typeName); - $providerDef = new DefinitionDecorator($abstractProviderId); - $providerDef->replaceArgument(0, $typeDef); - - // Doctrine has a mandatory service as second argument - $argPos = ('propel' === $typeConfig['driver']) ? 1 : 2; - - $providerDef->replaceArgument($argPos, new Reference($objectPersisterId)); - $providerDef->replaceArgument($argPos + 1, $typeConfig['model']); - - $options = array('batch_size' => $typeConfig['provider']['batch_size']); - - if ('propel' !== $typeConfig['driver']) { - $options['query_builder_method'] = $typeConfig['provider']['query_builder_method']; - $options['clear_object_manager'] = $typeConfig['provider']['clear_object_manager']; - } - - $providerDef->replaceArgument($argPos + 2, $options); + $providerDef = new DefinitionDecorator('foq_elastica.provider.prototype.' . $typeConfig['driver']); + $providerDef->addTag('foq_elastica.provider', array('index' => $indexName, 'type' => $typeName)); + $providerDef->replaceArgument(0, new Reference($objectPersisterId)); + $providerDef->replaceArgument(1, $typeConfig['model']); + // Propel provider can simply ignore Doctrine-specific options + $providerDef->replaceArgument(2, array_diff_key($typeConfig['provider'], array('service' => 1))); $container->setDefinition($providerId, $providerDef); return $providerId; @@ -371,24 +359,25 @@ class FOQElasticaExtension extends Extension /** * Loads the index manager * - * @return null + * @param array $indexRefsByName + * @param ContainerBuilder $container **/ - protected function loadIndexManager(array $indexDefs, $defaultIndexId, ContainerBuilder $container) + protected function loadIndexManager(array $indexRefsByName, ContainerBuilder $container) { $managerDef = $container->getDefinition('foq_elastica.index_manager'); - $managerDef->replaceArgument(0, $indexDefs); + $managerDef->replaceArgument(0, $indexRefsByName); $managerDef->replaceArgument(1, new Reference('foq_elastica.index')); } /** - * Loads the reseter + * Loads the resetter * * @return null **/ - protected function loadReseter(array $indexConfigs, ContainerBuilder $container) + protected function loadResetter(array $indexConfigs, ContainerBuilder $container) { - $reseterDef = $container->getDefinition('foq_elastica.reseter'); - $reseterDef->replaceArgument(0, $indexConfigs); + $resetterDef = $container->getDefinition('foq_elastica.resetter'); + $resetterDef->replaceArgument(0, $indexConfigs); } protected function loadDriver(ContainerBuilder $container, $driver) diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php index 38cf5ae..8f51e57 100644 --- a/Doctrine/AbstractProvider.php +++ b/Doctrine/AbstractProvider.php @@ -2,106 +2,85 @@ namespace FOQ\ElasticaBundle\Doctrine; -use FOQ\ElasticaBundle\Provider\ProviderInterface; +use Doctrine\Common\Persistence\ManagerRegistry; use FOQ\ElasticaBundle\Persister\ObjectPersisterInterface; -use Elastica_Type; -use Elastica_Document; -use Closure; -use InvalidArgumentException; +use FOQ\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider; -abstract class AbstractProvider implements ProviderInterface +abstract class AbstractProvider extends BaseAbstractProvider { - /** - * Elastica type - * - * @var Elastica_Type - */ - protected $type; + protected $managerRegistry; /** - * Manager registry + * Constructor. * - * @var object + * @param ObjectPersisterInterface $objectPersister + * @param string $objectClass + * @param array $options + * @param ManagerRegistry $managerRegistry */ - protected $registry; - - /** - * Object persister - * - * @var ObjectPersisterInterface - */ - protected $objectPersister; - - /** - * Provider options - * - * @var array - */ - protected $options = array( - 'batch_size' => 100, - 'clear_object_manager' => true, - 'query_builder_method' => 'createQueryBuilder' - ); - - public function __construct(Elastica_Type $type, $registry, ObjectPersisterInterface $objectPersister, $objectClass, array $options = array()) + public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options, $managerRegistry) { - $this->type = $type; - $this->registry = $registry; - $this->objectClass = $objectClass; - $this->objectPersister = $objectPersister; - $this->options = array_merge($this->options, $options); + parent::__construct($objectPersister, $objectClass, array_merge(array( + 'clear_object_manager' => true, + 'query_builder_method' => 'createQueryBuilder', + ), $options)); + + $this->managerRegistry = $managerRegistry; } /** - * Insert the repository objects in the type index - * - * @param Closure $loggerClosure + * @see FOQ\ElasticaBundle\Provider\ProviderInterface::populate() */ - public function populate(Closure $loggerClosure) + public function populate(\Closure $loggerClosure = null) { $queryBuilder = $this->createQueryBuilder(); - $nbObjects = $this->countObjects($queryBuilder); + $nbObjects = $this->countObjects($queryBuilder); for ($offset = 0; $offset < $nbObjects; $offset += $this->options['batch_size']) { + if ($loggerClosure) { + $stepStartTime = microtime(true); + } - $stepStartTime = microtime(true); $objects = $this->fetchSlice($queryBuilder, $this->options['batch_size'], $offset); $this->objectPersister->insertMany($objects); if ($this->options['clear_object_manager']) { - $this->registry->getManagerForClass($this->objectClass)->clear(); + $this->managerRegistry->getManagerForClass($this->objectClass)->clear(); } - $stepNbObjects = count($objects); - $stepCount = $stepNbObjects+$offset; - $objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime); - $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', 100*$stepCount/$nbObjects, $stepCount, $nbObjects, $objectsPerSecond)); + if ($loggerClosure) { + $stepNbObjects = count($objects); + $stepCount = $stepNbObjects + $offset; + $percentComplete = 100 * $stepCount / $nbObjects; + $objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime); + $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond)); + } } } /** - * Counts the objects of a query builder + * Counts objects that would be indexed using the query builder. * - * @param queryBuilder - * @return int - **/ + * @param object $queryBuilder + * @return integer + */ protected abstract function countObjects($queryBuilder); /** - * Fetches a slice of objects + * Fetches a slice of objects using the query builder. * - * @param queryBuilder - * @param int limit - * @param int offset - * @return array of objects - **/ + * @param object $queryBuilder + * @param integer $limit + * @param integer $offset + * @return array + */ protected abstract function fetchSlice($queryBuilder, $limit, $offset); /** - * Creates the query builder used to fetch the documents to index + * Creates the query builder, which will be used to fetch objects to index. * - * @return query builder - **/ + * @return object + */ protected abstract function createQueryBuilder(); } diff --git a/Doctrine/MongoDB/Provider.php b/Doctrine/MongoDB/Provider.php index 995c8c0..1d65975 100644 --- a/Doctrine/MongoDB/Provider.php +++ b/Doctrine/MongoDB/Provider.php @@ -2,42 +2,49 @@ namespace FOQ\ElasticaBundle\Doctrine\MongoDB; +use Doctrine\ODM\MongoDB\Query\Builder; use FOQ\ElasticaBundle\Doctrine\AbstractProvider; +use FOQ\ElasticaBundle\Exception\InvalidArgumentTypeException; class Provider extends AbstractProvider { /** - * Counts the objects of a query builder - * - * @param queryBuilder - * @return int - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::countObjects() + */ protected function countObjects($queryBuilder) { - return $queryBuilder->getQuery()->count(); + if (!$queryBuilder instanceof Builder) { + throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ODM\MongoDB\Query\Builder'); + } + + return $queryBuilder + ->getQuery() + ->count(); } /** - * Fetches a slice of objects - * - * @param queryBuilder - * @param int limit - * @param int offset - * @return array of objects - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice() + */ protected function fetchSlice($queryBuilder, $limit, $offset) { - return $queryBuilder->limit($limit)->skip($offset)->getQuery()->execute()->toArray(); + if (!$queryBuilder instanceof Builder) { + throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ODM\MongoDB\Query\Builder'); + } + + return $queryBuilder + ->limit($limit) + ->skip($offset) + ->getQuery() + ->execute() + ->toArray(); } /** - * Creates the query builder used to fetch the documents to index - * - * @return query builder - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder() + */ protected function createQueryBuilder() { - return $this->registry + return $this->managerRegistry ->getManagerForClass($this->objectClass) ->getRepository($this->objectClass) ->{$this->options['query_builder_method']}(); diff --git a/Doctrine/ORM/Provider.php b/Doctrine/ORM/Provider.php index bbef3d1..8947536 100644 --- a/Doctrine/ORM/Provider.php +++ b/Doctrine/ORM/Provider.php @@ -2,51 +2,59 @@ namespace FOQ\ElasticaBundle\Doctrine\ORM; +use Doctrine\ORM\QueryBuilder; use FOQ\ElasticaBundle\Doctrine\AbstractProvider; +use FOQ\ElasticaBundle\Exception\InvalidArgumentTypeException; class Provider extends AbstractProvider { /** - * Counts the objects of a query builder - * - * @param queryBuilder - * @return int - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::countObjects() + */ protected function countObjects($queryBuilder) { - $qb = clone $queryBuilder; - $qb->select($qb->expr()->count($queryBuilder->getRootAlias())) - ->resetDQLPart('orderBy'); // no need to order the query. It does not change the count and make the query less efficient. + if (!$queryBuilder instanceof QueryBuilder) { + throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); + } - return $qb->getQuery()->getSingleScalarResult(); + /* Clone the query builder before altering its field selection and DQL, + * lest we leave the query builder in a bad state for fetchSlice(). + */ + $qb = clone $queryBuilder; + + return $qb + ->select($qb->expr()->count($queryBuilder->getRootAlias())) + // Remove ordering for efficiency; it doesn't affect the count + ->resetDQLPart('orderBy') + ->getQuery() + ->getSingleScalarResult(); } /** - * Fetches a slice of objects - * - * @param queryBuilder - * @param int limit - * @param int offset - * @return array of objects - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice() + */ protected function fetchSlice($queryBuilder, $limit, $offset) { - $queryBuilder->setFirstResult($offset); - $queryBuilder->setMaxResults($limit); + if (!$queryBuilder instanceof QueryBuilder) { + throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); + } - return $queryBuilder->getQuery()->getResult(); + return $queryBuilder + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); } /** - * Creates the query builder used to fetch the documents to index - * - * @return query builder - **/ + * @see FOQ\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder() + */ protected function createQueryBuilder() { - return $this->registry + return $this->managerRegistry ->getManagerForClass($this->objectClass) ->getRepository($this->objectClass) + // ORM query builders require an alias argument ->{$this->options['query_builder_method']}('a'); } } diff --git a/Exception/InvalidArgumentTypeException.php b/Exception/InvalidArgumentTypeException.php new file mode 100644 index 0000000..fc319a8 --- /dev/null +++ b/Exception/InvalidArgumentTypeException.php @@ -0,0 +1,11 @@ +addCompilerPass(new AddProviderPass()); + $container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new TransformerPass()); } } diff --git a/IndexManager.php b/IndexManager.php index 96a6e2c..3891088 100644 --- a/IndexManager.php +++ b/IndexManager.php @@ -2,17 +2,21 @@ namespace FOQ\ElasticaBundle; -use InvalidArgumentException; - class IndexManager { - protected $indexes; - protected $defaultIndex; + protected $indexesByName; + protected $defaultIndexName; - public function __construct(array $indexes, $defaultIndex) + /** + * Constructor. + * + * @param array $indexesByName + * @param string $defaultIndexName + */ + public function __construct(array $indexesByName, $defaultIndexName) { - $this->indexes = $indexes; - $this->defaultIndex = $defaultIndex; + $this->indexesByName = $indexesByName; + $this->defaultIndexName = $defaultIndexName; } /** @@ -22,33 +26,36 @@ class IndexManager */ public function getAllIndexes() { - return $this->indexes; + return $this->indexesByName; } /** * Gets an index by its name * + * @param string $name Index to return, or the default index if null * @return Elastica_Index - **/ - public function getIndex($name) + * @throws InvalidArgumentException if no index exists for the given name + */ + public function getIndex($name = null) { - if (!$name) { - return $this->getDefaultIndex(); - } - if (!isset($this->indexes[$name])) { - throw new InvalidArgumentException(sprintf('The index "%s" does not exist', $name)); + if (null === $name) { + $name = $this->defaultIndexName; } - return $this->indexes[$name]; + if (!isset($this->indexesByName[$name])) { + throw new \InvalidArgumentException(sprintf('The index "%s" does not exist', $name)); + } + + return $this->indexesByName[$name]; } /** * Gets the default index * * @return Elastica_Index - **/ + */ public function getDefaultIndex() { - return $this->defaultIndex; + return $this->getIndex($this->defaultIndexName); } } diff --git a/MappingRegistry.php b/MappingRegistry.php deleted file mode 100644 index 3573e9f..0000000 --- a/MappingRegistry.php +++ /dev/null @@ -1,60 +0,0 @@ - array(type_object, mapping_array) - * ) - * - * @var array - */ - protected $mappings = null; - - /** - * Instanciates a new MappingSetter - * - * @param array mappings - */ - public function __construct($mappings) - { - $this->mappings = $mappings; - } - - /** - * Apply mappings to all elastica types - **/ - public function applyMappings() - { - foreach ($this->mappings as $pair) { - list($type, $mappings) = $pair; - $type->setMapping($mappings); - } - } - - /** - * Gets the type mapping field names - * - * @param Elastica_Type $type - * @return array list of fields names - */ - public function getTypeFieldNames(Elastica_Type $type) - { - $key = sprintf('%s/%s', $type->getIndex()->getName(), $type->getType()); - if (!isset($this->mappings[$key])) { - throw new InvalidArgumentException(sprintf('This type is not registered: "%s".', $key)); - } - - return array_keys($this->mappings[$key][1]); - } -} diff --git a/Populator.php b/Populator.php deleted file mode 100755 index 751aebf..0000000 --- a/Populator.php +++ /dev/null @@ -1,32 +0,0 @@ - $provider) { - $this->addProvider($name, $provider); - } - } - - public function addProvider($name, ProviderInterface $provider) - { - $this->providers[$name] = $provider; - } - - public function populate(Closure $loggerClosure) - { - foreach ($this->providers as $name => $provider) { - $provider->populate(function($text) use ($name, $loggerClosure) { - $loggerClosure(sprintf('Indexing %s, %s', $name, $text)); - }); - } - } -} diff --git a/Propel/Provider.php b/Propel/Provider.php index 46d28a2..141a558 100644 --- a/Propel/Provider.php +++ b/Propel/Provider.php @@ -2,64 +2,28 @@ namespace FOQ\ElasticaBundle\Propel; -use FOQ\ElasticaBundle\Provider\ProviderInterface; -use FOQ\ElasticaBundle\Persister\ObjectPersisterInterface; -use Elastica_Type; -use Elastica_Document; -use Closure; -use InvalidArgumentException; +use FOQ\ElasticaBundle\Provider\AbstractProvider; /** * Propel provider * * @author William Durand */ -class Provider implements ProviderInterface +class Provider extends AbstractProvider { /** - * Elastica type - * - * @var Elastica_Type + * @see FOQ\ElasticaBundle\Provider\ProviderInterface::populate() */ - protected $type; - - /** - * Object persister - * - * @var ObjectPersisterInterface - */ - protected $objectPersister; - - /** - * Provider options - * - * @var array - */ - protected $options = array( - 'batch_size' => 100, - ); - - public function __construct(Elastica_Type $type, ObjectPersisterInterface $objectPersister, $objectClass, array $options = array()) - { - $this->type = $type; - $this->objectClass = $objectClass; - $this->objectPersister = $objectPersister; - $this->options = array_merge($this->options, $options); - } - - /** - * Insert the repository objects in the type index - * - * @param Closure $loggerClosure - */ - public function populate(Closure $loggerClosure) + public function populate(\Closure $loggerClosure = null) { $queryClass = $this->objectClass . 'Query'; - $nbObjects = $queryClass::create()->count(); + $nbObjects = $queryClass::create()->count(); for ($offset = 0; $offset < $nbObjects; $offset += $this->options['batch_size']) { + if ($loggerClosure) { + $stepStartTime = microtime(true); + } - $stepStartTime = microtime(true); $objects = $queryClass::create() ->limit($this->options['batch_size']) ->offset($offset) @@ -67,10 +31,13 @@ class Provider implements ProviderInterface $this->objectPersister->insertMany($objects->getArrayCopy()); - $stepNbObjects = count($objects); - $stepCount = $stepNbObjects+$offset; - $objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime); - $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', 100*$stepCount/$nbObjects, $stepCount, $nbObjects, $objectsPerSecond)); + if ($loggerClosure) { + $stepNbObjects = count($objects); + $stepCount = $stepNbObjects + $offset; + $percentComplete = 100 * $stepCount / $nbObjects; + $objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime); + $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond)); + } } } } diff --git a/Provider/AbstractProvider.php b/Provider/AbstractProvider.php new file mode 100644 index 0000000..04c5b9e --- /dev/null +++ b/Provider/AbstractProvider.php @@ -0,0 +1,30 @@ +objectPersister = $objectPersister; + $this->objectClass = $objectClass; + + $this->options = array_merge(array( + 'batch_size' => 100, + ), $options); + } +} diff --git a/Provider/ProviderInterface.php b/Provider/ProviderInterface.php index 097e2c6..5e81cd6 100644 --- a/Provider/ProviderInterface.php +++ b/Provider/ProviderInterface.php @@ -2,8 +2,6 @@ namespace FOQ\ElasticaBundle\Provider; -use Closure; - /** * Insert application domain objects into elastica types * @@ -12,9 +10,9 @@ use Closure; interface ProviderInterface { /** - * Add all domain objects of a repository to the elastica type + * Persists all domain objects to ElasticSearch for this provider. * * @param Closure $loggerClosure */ - function populate(Closure $loggerClosure); + function populate(\Closure $loggerClosure = null); } diff --git a/Provider/ProviderRegistry.php b/Provider/ProviderRegistry.php new file mode 100644 index 0000000..e9abe9f --- /dev/null +++ b/Provider/ProviderRegistry.php @@ -0,0 +1,102 @@ +providers[$index])) { + $this->providers[$index] = array(); + } + + $this->providers[$index][$type] = $providerId; + } + + /** + * Gets all registered providers. + * + * Providers will be indexed by "index/type" strings in the returned array. + * + * @return array of ProviderInterface instances + */ + public function getAllProviders() + { + $providers = array(); + + foreach ($this->providers as $index => $indexProviders) { + foreach ($indexProviders as $type => $providerId) { + $providers[sprintf('%s/%s', $index, $type)] = $this->container->get($providerId); + } + } + + return $providers; + } + + /** + * Gets all providers for an index. + * + * Providers will be indexed by "type" strings in the returned array. + * + * @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 $type => $providerId) { + $providers[$type] = $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; + } +} diff --git a/README.md b/README.md index 4c6b0c2..d03f9aa 100644 --- a/README.md +++ b/README.md @@ -203,20 +203,20 @@ You can change this value in the provider configuration. ##### Change the document identifier field By default, ElasticaBundle will use the `id` field of your entities as the elasticsearch document identifier. -You can change this value in the provider configuration. +You can change this value in the persistence configuration. persistence: driver: orm model: Application\UserBundle\Entity\User - provider: - identifier: id + identifier: id #### Manual provider -Create a service with the tag "foq_elastica.provider". +Create a service with the tag "foq_elastica.provider" and attributes for the +index and type for which the service will provide. - + @@ -243,9 +243,11 @@ Its class must implement `FOQ\ElasticaBundle\Provider\ProviderInterface`. * * @param Closure $loggerClosure */ - public function populate(Closure $loggerClosure) + public function populate(Closure $loggerClosure = null) { - $loggerClosure('Indexing users'); + if ($loggerClosure) { + $loggerClosure('Indexing users'); + } $this->userType->addDocuments(array( array('username' => 'Bob') diff --git a/Reseter.php b/Reseter.php deleted file mode 100644 index 98a7ee0..0000000 --- a/Reseter.php +++ /dev/null @@ -1,35 +0,0 @@ -indexConfigs = $indexConfigs; - } - - /** - * Resets all indexes - * - * @return null - **/ - public function reset() - { - foreach ($this->indexConfigs as $indexConfig) { - $indexConfig['index']->create($indexConfig['config'], true); - } - } -} diff --git a/Resetter.php b/Resetter.php new file mode 100644 index 0000000..d37a6b1 --- /dev/null +++ b/Resetter.php @@ -0,0 +1,79 @@ +indexConfigsByName = $indexConfigsByName; + } + + /** + * Deletes and recreates all indexes + */ + public function resetAllIndexes() + { + foreach ($this->indexConfigsByName as $indexConfig) { + $indexConfig['index']->create($indexConfig['config'], true); + } + } + + /** + * Deletes and recreates the named index + * + * @param string $indexName + * @throws InvalidArgumentException if no index exists for the given name + */ + public function resetIndex($indexName) + { + $indexConfig = $this->getIndexConfig($indexName); + $indexConfig['index']->create($indexConfig['config'], true); + } + + /** + * Deletes and recreates a mapping type for the named index + * + * @param string $indexName + * @param string $typeName + * @throws InvalidArgumentException if no index or type mapping exists for the given names + */ + public function resetIndexType($indexName, $typeName) + { + $indexConfig = $this->getIndexConfig($indexName); + + if (!isset($indexConfig['config']['mappings'][$typeName])) { + throw new \InvalidArgumentException(sprintf('The mapping for index "%s" and type "%s" does not exist.', $indexName, $typeName)); + } + + $type = $indexConfig['index']->getType($typeName); + $type->delete(); + $type->setMapping($indexConfig['config']['mappings'][$typeName]); + } + + /** + * Gets an index config by its name + * + * @param string $index Index name + * @return array + * @throws InvalidArgumentException if no index config exists for the given name + */ + protected function getIndexConfig($indexName) + { + if (!isset($this->indexConfigsByName[$indexName])) { + throw new \InvalidArgumentException(sprintf('The configuration for index "%s" does not exist.', $indexName)); + } + + return $this->indexConfigsByName[$indexName]; + } +} diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 2549d59..0c0adb5 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -12,6 +12,7 @@ FOQ\ElasticaBundle\DataCollector\ElasticaDataCollector FOQ\ElasticaBundle\Manager\RepositoryManager FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection + FOQ\ElasticaBundle\Provider\ProviderRegistry @@ -37,11 +38,7 @@ - - - - - + @@ -66,6 +63,11 @@ + + + + + diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml index a1bd8d3..4a3e22b 100644 --- a/Resources/config/mongodb.xml +++ b/Resources/config/mongodb.xml @@ -6,12 +6,11 @@ - - - + + diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml index 3803853..861e2b4 100644 --- a/Resources/config/orm.xml +++ b/Resources/config/orm.xml @@ -6,12 +6,11 @@ - - - + + diff --git a/Resources/config/propel.xml b/Resources/config/propel.xml index 39c7a31..e8fd744 100644 --- a/Resources/config/propel.xml +++ b/Resources/config/propel.xml @@ -5,8 +5,7 @@ - - + diff --git a/Tests/DependencyInjection/Compiler/RegisterProvidersPassTest.php b/Tests/DependencyInjection/Compiler/RegisterProvidersPassTest.php new file mode 100644 index 0000000..dbbfcb0 --- /dev/null +++ b/Tests/DependencyInjection/Compiler/RegisterProvidersPassTest.php @@ -0,0 +1,78 @@ +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; + } +} diff --git a/Tests/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php new file mode 100644 index 0000000..9cc7bd5 --- /dev/null +++ b/Tests/Doctrine/AbstractProviderTest.php @@ -0,0 +1,183 @@ +markTestSkipped('Doctrine Common is not available.'); + } + + $this->objectClass = 'objectClass'; + $this->options = array(); + + $this->objectPersister = $this->getMockObjectPersister(); + $this->managerRegistry = $this->getMockManagerRegistry(); + $this->objectManager = $this->getMockObjectManager(); + + $this->managerRegistry->expects($this->any()) + ->method('getManagerForClass') + ->with($this->objectClass) + ->will($this->returnValue($this->objectManager)); + } + + /** + * @dataProvider providePopulateIterations + */ + public function testPopulateIterations($nbObjects, $objectsByIteration, $batchSize) + { + $this->options['batch_size'] = $batchSize; + + $provider = $this->getMockAbstractProvider(); + + $queryBuilder = new \stdClass(); + + $provider->expects($this->once()) + ->method('createQueryBuilder') + ->will($this->returnValue($queryBuilder)); + + $provider->expects($this->once()) + ->method('countObjects') + ->with($queryBuilder) + ->will($this->returnValue($nbObjects)); + + $providerInvocationOffset = 2; + + foreach ($objectsByIteration as $i => $objects) { + $offset = $objects[0] - 1; + + $provider->expects($this->at($providerInvocationOffset + $i)) + ->method('fetchSlice') + ->with($queryBuilder, $batchSize, $offset) + ->will($this->returnValue($objects)); + + $this->objectPersister->expects($this->at($i)) + ->method('insertMany') + ->with($objects); + + $this->objectManager->expects($this->at($i)) + ->method('clear'); + } + + $provider->populate(); + } + + public function providePopulateIterations() + { + return array( + array( + 100, + array(range(1,100)), + 100, + ), + array( + 105, + array(range(1, 50), range(51, 100), range(101, 105)), + 50, + ), + ); + } + + public function testPopulateShouldNotClearObjectManager() + { + $nbObjects = 1; + $objects = array(1); + $this->options['clear_object_manager'] = false; + + $provider = $this->getMockAbstractProvider(); + + $provider->expects($this->any()) + ->method('countObjects') + ->will($this->returnValue($nbObjects)); + + $provider->expects($this->any()) + ->method('fetchSlice') + ->will($this->returnValue($objects)); + + $this->objectManager->expects($this->never()) + ->method('clear'); + + $provider->populate(); + } + + public function testPopulateInvokesLoggerClosure() + { + $nbObjects = 1; + $objects = array(1); + + $provider = $this->getMockAbstractProvider(); + + $provider->expects($this->any()) + ->method('countObjects') + ->will($this->returnValue($nbObjects)); + + $provider->expects($this->any()) + ->method('fetchSlice') + ->will($this->returnValue($objects)); + + $loggerClosureInvoked = false; + $loggerClosure = function () use (&$loggerClosureInvoked) { + $loggerClosureInvoked = true; + }; + + $provider->populate(); + $this->assertFalse($loggerClosureInvoked); + + $provider->populate($loggerClosure); + $this->assertTrue($loggerClosureInvoked); + } + + /** + * @return FOQ\ElasticaBundle\Doctrine\AbstractProvider + */ + private function getMockAbstractProvider() + { + return $this->getMockForAbstractClass('FOQ\ElasticaBundle\Doctrine\AbstractProvider', array( + $this->objectPersister, + $this->objectClass, + $this->options, + $this->managerRegistry, + )); + } + + /** + * @return Doctrine\Common\Persistence\ManagerRegistry + */ + private function getMockManagerRegistry() + { + return $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + } + + /** + * @return FOQ\ElasticaBundle\Tests\Doctrine\ObjectManager + */ + private function getMockObjectManager() + { + return $this->getMock(__NAMESPACE__ . '\ObjectManager'); + } + + /** + * @return FOQ\ElasticaBundle\Persister\ObjectPersisterInterface + */ + private function getMockObjectPersister() + { + return $this->getMock('FOQ\ElasticaBundle\Persister\ObjectPersisterInterface'); + } +} + +/** + * Doctrine\Common\Persistence\ObjectManager does not include a clear() method + * in its interface, so create a new interface for mocking. + */ +interface ObjectManager +{ + function clear(); +} diff --git a/Tests/IndexManagerTest.php b/Tests/IndexManagerTest.php index 897c3bb..1eec086 100644 --- a/Tests/IndexManagerTest.php +++ b/Tests/IndexManagerTest.php @@ -6,37 +6,42 @@ use FOQ\ElasticaBundle\IndexManager; class IndexManagerTest extends \PHPUnit_Framework_TestCase { - /** - * @var FOQ\ElasticaBundle\Tests\IndexManager - */ - private $indexManager = null; + private $defaultIndexName; + private $indexesByName; + private $indexManager; public function setUp() { - $this->indexManager = new IndexManager(array('index1' => 'test1', 'index2' => 'test2'), 'defaultIndex'); + $this->defaultIndexName = 'index2'; + $this->indexesByName = array( + 'index1' => 'test1', + 'index2' => 'test2', + ); + $this->indexManager = new IndexManager($this->indexesByName, $this->defaultIndexName); } - public function testThatWeCanGetAllIndexes() + public function testGetAllIndexes() { - $this->assertEquals(array('index1' => 'test1', 'index2' => 'test2'), $this->indexManager->getAllIndexes()); + $this->assertEquals($this->indexesByName, $this->indexManager->getAllIndexes()); + } + + public function testGetIndex() + { + $this->assertEquals($this->indexesByName['index1'], $this->indexManager->getIndex('index1')); + $this->assertEquals($this->indexesByName['index2'], $this->indexManager->getIndex('index2')); } /** * @expectedException InvalidArgumentException */ - public function testThatWeCannotGetIndexWhichWasNotSet() + public function testGetIndexShouldThrowExceptionForInvalidName() { - $this->indexManager->getIndex('index8'); + $this->indexManager->getIndex('index3'); } - - public function testThatWeCanGetDefaultIndex() + + public function testGetDefaultIndex() { - $this->assertEquals('defaultIndex', $this->indexManager->getIndex(false)); - $this->assertEquals('defaultIndex', $this->indexManager->getDefaultIndex()); - } - - public function testThatWeCanGetIndex() - { - $this->assertEquals('test2', $this->indexManager->getIndex('index2')); + $this->assertEquals('test2', $this->indexManager->getIndex()); + $this->assertEquals('test2', $this->indexManager->getDefaultIndex()); } } diff --git a/Tests/MappingRegistryTest.php b/Tests/MappingRegistryTest.php deleted file mode 100644 index 197eb29..0000000 --- a/Tests/MappingRegistryTest.php +++ /dev/null @@ -1,91 +0,0 @@ -markTestSkipped('The Elastica library classes are not available'); - } - } - - public function testThatCanApplyMappings() - { - $typeMock = $this->getMockBuilder('Elastica_Type') - ->disableOriginalConstructor() - ->getMock(); - - $typeMock->expects($this->once()) - ->method('setMapping') - ->with($this->equalTo(array('mappingArray'))); - - $mapping = new MappingRegistry(array( - 'index/type' => array($typeMock, array('mappingArray')) - )); - - $mapping->applyMappings(); - } - - /** - * @dataProvider invalidTypesParametersProvider - * @expectedException InvalidArgumentException - */ - public function testThatCannotGetTypeFieldForTypeWhichNotExists($indexName, $typeName) - { - $type = $this->getTypeMock('index', 'type'); - $mapping = new MappingRegistry(array( - 'index/type' => array($type, array('mappingArray')) - )); - - $mapping->getTypeFieldNames($this->getTypeMock($indexName, $typeName)); - } - - public function testThatCanGetTypeField() - { - $type = $this->getTypeMock('index', 'type'); - $mapping = new MappingRegistry(array( - 'index/type' => array($type, array('mappingArray')) - )); - - $mapping->getTypeFieldNames($this->getTypeMock('index', 'type')); - } - - public static function invalidTypesParametersProvider() - { - return array( - array('index1', 'type'), - array('index', 'type2') - ); - } - - private function getTypeMock($indexName, $typeName) - { - $typeMock = $this->getMockBuilder('Elastica_Type') - ->disableOriginalConstructor() - ->getMock(); - - $indexMock = $this->getMockBuilder('Elastica_Index') - ->disableOriginalConstructor() - ->getMock(); - - $indexMock->expects($this->any()) - ->method('getName') - ->will($this->returnValue($indexName)); - - $typeMock->expects($this->any()) - ->method('getIndex') - ->will($this->returnValue($indexMock)); - - $typeMock->expects($this->any()) - ->method('getType') - ->will($this->returnValue($typeName)); - - return $typeMock; - } -} diff --git a/Tests/PopulatorTest.php b/Tests/PopulatorTest.php deleted file mode 100755 index 6c2e44d..0000000 --- a/Tests/PopulatorTest.php +++ /dev/null @@ -1,60 +0,0 @@ -getMock('FOQ\ElasticaBundle\Provider\ProviderInterface', array('populate')); - - $populator = new PopulatorMock(array()); - $populator->addProvider('l3l0Provider', $provider); - - $this->assertEquals(count($populator->providers), 1); - $this->assertArrayHasKey('l3l0Provider', $populator->providers); - $this->assertInstanceOf('FOQ\ElasticaBundle\Provider\ProviderInterface', $populator->providers['l3l0Provider']); - } - - public function testThatPopulateThroughProviders() - { - $provider = $this->getMock('FOQ\ElasticaBundle\Provider\ProviderInterface', array('populate')); - $provider->expects($this->once()) - ->method('populate'); - - $provider2 = $this->getMock('FOQ\ElasticaBundle\Provider\ProviderInterface', array('populate')); - $provider2->expects($this->once()) - ->method('populate'); - - $populator = new Populator(array('l3l0Provider' => $provider, 'secondProvider' => $provider2)); - $populator->populate(function ($text) { return $text; }); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testThatAddProviderHaveToImpelementProviderInterface() - { - $populator = new Populator(array()); - $populator->addProvider('provider', new \stdClass()); - $populator->populate(function ($text) { return $text; }); - } - - /** - * @expectedException PHPUnit_Framework_Error - */ - public function testThatProvidersPassToTheContructorHaveToImplementProviderInterface() - { - $populator = new Populator(array('provider' => new \stdClass())); - $populator->populate(function ($text) { return $text; }); - } -} diff --git a/Tests/Provider/ProviderRegistryTest.php b/Tests/Provider/ProviderRegistryTest.php new file mode 100644 index 0000000..8582aab --- /dev/null +++ b/Tests/Provider/ProviderRegistryTest.php @@ -0,0 +1,86 @@ +container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + // Mock ContainerInterface::get() to return the service ID + $this->container->expects($this->any()) + ->method('get') + ->will($this->returnArgument(0)); + + $this->registry = new ProviderRegistry(); + $this->registry->setContainer($this->container); + + $this->registry->addProvider('foo', 'a', 'provider.foo.a'); + $this->registry->addProvider('foo', 'b', 'provider.foo.b'); + $this->registry->addProvider('foo', 'c', 'provider.foo.c'); + $this->registry->addProvider('bar', 'a', 'provider.bar.a'); + $this->registry->addProvider('bar', 'b', 'provider.bar.b'); + } + + public function testGetAllProviders() + { + $allProviders = array( + 'foo/a' => 'provider.foo.a', + 'foo/b' => 'provider.foo.b', + 'foo/c' => 'provider.foo.c', + 'bar/a' => 'provider.bar.a', + 'bar/b' => 'provider.bar.b', + ); + + $this->assertEquals($allProviders, $this->registry->getAllProviders()); + } + + public function testGetIndexProviders() + { + $fooProviders = array( + 'a' => 'provider.foo.a', + 'b' => 'provider.foo.b', + 'c' => 'provider.foo.c', + ); + + $barProviders = array( + 'a' => 'provider.bar.a', + 'b' => 'provider.bar.b', + ); + + $this->assertEquals($fooProviders, $this->registry->getIndexProviders('foo')); + $this->assertEquals($barProviders, $this->registry->getIndexProviders('bar')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetIndexProvidersWithInvalidIndex() + { + $this->registry->getIndexProviders('baz'); + } + + public function testGetProvider() + { + $this->assertEquals('provider.foo.a', $this->registry->getProvider('foo', 'a')); + $this->assertEquals('provider.foo.b', $this->registry->getProvider('foo', 'b')); + $this->assertEquals('provider.foo.c', $this->registry->getProvider('foo', 'c')); + $this->assertEquals('provider.bar.a', $this->registry->getProvider('bar', 'a')); + $this->assertEquals('provider.bar.b', $this->registry->getProvider('bar', 'b')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetProviderWithInvalidIndexAndType() + { + $this->registry->getProvider('bar', 'c'); + } +} diff --git a/Tests/ReseterTest.php b/Tests/ReseterTest.php deleted file mode 100644 index 598ae08..0000000 --- a/Tests/ReseterTest.php +++ /dev/null @@ -1,71 +0,0 @@ -deleted = true; - } - - public function create() - { - $this->created = true; - } -} - -class NewIndex -{ - public $deleted = false; - public $created = false; - - public function delete() - { - $jsonResponse = json_encode(array('index' => 'is_new')); - - throw new Elastica_Exception_Response(new Elastica_Response($jsonResponse)); - } - - public function create() - { - $this->created = true; - } -} - -class ReseterTest extends \PHPUnit_Framework_TestCase -{ - public function setUp() - { - if (!class_exists('Elastica_Exception_Response') || !class_exists('Elastica_Response')) { - $this->markTestSkipped('The Elastica library classes are not available'); - } - } - - public function testThatResetMethodRecreateAllIndexes() - { - $indexConfig = array(); - $indexConfig['index_1'] = array(); - $indexConfig['index_1']['index'] = new Index(); - $indexConfig['index_1']['config'] = array(); - $indexConfig['index_2'] = array(); - $indexConfig['index_2']['index'] = new Index(); - $indexConfig['index_2']['config'] = array(); - - - $reseter = new Reseter($indexConfig); - $reseter->reset(); - - $this->assertTrue($indexConfig['index_1']['index']->created); - $this->assertTrue($indexConfig['index_2']['index']->created); - } - -} diff --git a/Tests/ResetterTest.php b/Tests/ResetterTest.php new file mode 100644 index 0000000..5ce7912 --- /dev/null +++ b/Tests/ResetterTest.php @@ -0,0 +1,138 @@ +indexConfigsByName = array( + 'foo' => array( + 'index' => $this->getMockElasticaIndex(), + 'config' => array( + 'mappings' => array( + 'a' => $this->getMockElasticaTypeMapping(), + 'b' => $this->getMockElasticaTypeMapping(), + ), + ), + ), + 'bar' => array( + 'index' => $this->getMockElasticaIndex(), + 'config' => array( + 'mappings' => array( + 'a' => $this->getMockElasticaTypeMapping(), + 'b' => $this->getMockElasticaTypeMapping(), + ), + ), + ), + ); + } + + public function testResetAllIndexes() + { + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['foo']['config'], true); + + $this->indexConfigsByName['bar']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['bar']['config'], true); + + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetAllIndexes(); + } + + public function testResetIndex() + { + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['foo']['config'], true); + + $this->indexConfigsByName['bar']['index']->expects($this->never()) + ->method('create'); + + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndex('foo'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testResetIndexShouldThrowExceptionForInvalidIndex() + { + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndex('baz'); + } + + public function testResetIndexType() + { + $type = $this->getMockElasticaType(); + + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('getType') + ->with('a') + ->will($this->returnValue($type)); + + $type->expects($this->once()) + ->method('delete'); + + $type->expects($this->once()) + ->method('setMapping') + ->with($this->indexConfigsByName['foo']['config']['mappings']['a']); + + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('foo', 'a'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testResetIndexTypeShouldThrowExceptionForInvalidIndex() + { + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('baz', 'a'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testResetIndexTypeShouldThrowExceptionForInvalidType() + { + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('foo', 'c'); + } + + /** + * @return Elastica_Index + */ + private function getMockElasticaIndex() + { + return $this->getMockBuilder('Elastica_Index') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return Elastica_Type + */ + private function getMockElasticaType() + { + return $this->getMockBuilder('Elastica_Type') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @return Elastica_Type_Mapping + */ + private function getMockElasticaTypeMapping() + { + return $this->getMockBuilder('Elastica_Type_Mapping') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/composer.json b/composer.json index 8c87f11..1847b17 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "php": ">=5.3.2", "symfony/framework-bundle": "2.1.*", "symfony/console": "2.1.*", - "ruflin/elastica": "*" + "ruflin/elastica": ">=0.19.0" }, "autoload": { "psr-0": { "FOQ\\ElasticaBundle": "" }