Merge pull request #80 from Exercise/repopulate-index

Allow single indexes and types to be repopulated
This commit is contained in:
Jeremy Mikola 2012-03-13 10:48:07 -07:00
commit 661c324e43
33 changed files with 1094 additions and 647 deletions

View file

@ -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();
}
}

View file

@ -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')
;
}
/**

View file

@ -1,27 +0,0 @@
<?php
namespace FOQ\ElasticaBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class AddProviderPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->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);
}
}

View file

@ -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];
}
}

View file

@ -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)

View file

@ -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();
}

View file

@ -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']}();

View file

@ -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');
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace FOQ\ElasticaBundle\Exception;
class InvalidArgumentTypeException extends \InvalidArgumentException
{
public function __construct($value, $expectedType)
{
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}

View file

@ -2,18 +2,22 @@
namespace FOQ\ElasticaBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\AddProviderPass;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class FOQElasticaBundle extends Bundle
{
/**
* @see Symfony\Component\HttpKernel\Bundle\Bundle::build()
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AddProviderPass());
$container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TransformerPass());
}
}

View file

@ -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);
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace FOQ\ElasticaBundle;
use Elastica_Type;
use InvalidArgumentException;
/**
* Stores the configured mappings for all types
* Responsible for applying configured mappings to elastica types
*/
class MappingRegistry
{
/**
* Configured mappings. See http://www.elasticsearch.org/guide/reference/mapping/
* array(
* "index_name/type_name" => 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]);
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace FOQ\ElasticaBundle;
use FOQ\ElasticaBundle\Provider\ProviderInterface;
use Closure;
class Populator
{
protected $providers;
public function __construct(array $providers)
{
foreach ($providers as $name => $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));
});
}
}
}

View file

@ -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 <william.durand1@gmail.com>
*/
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));
}
}
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace FOQ\ElasticaBundle\Provider;
use FOQ\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOQ\ElasticaBundle\Provider\ProviderInterface;
abstract class AbstractProvider implements ProviderInterface
{
protected $objectClass;
protected $objectPersister;
protected $options;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param string $objectClass
* @param array $options
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options = array())
{
$this->objectPersister = $objectPersister;
$this->objectClass = $objectClass;
$this->options = array_merge(array(
'batch_size' => 100,
), $options);
}
}

View file

@ -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);
}

View file

@ -0,0 +1,102 @@
<?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.
*
* 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;
}
}

View file

@ -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.
<service id="acme.search_provider.user" class="Acme\UserBundle\Search\UserProvider">
<tag name="foq_elastica.provider" />
<tag name="foq_elastica.provider" index="website" type="user" />
<argument type="service" id="foq_elastica.index.website.user" />
</service>
@ -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')

View file

@ -1,35 +0,0 @@
<?php
namespace FOQ\ElasticaBundle;
use Elastica_Exception_Response;
/**
* Deletes and recreates indexes
**/
class Reseter
{
/**
* Index settings and mappings
*
* @var array
*/
protected $indexConfigs;
public function __construct(array $indexConfigs)
{
$this->indexConfigs = $indexConfigs;
}
/**
* Resets all indexes
*
* @return null
**/
public function reset()
{
foreach ($this->indexConfigs as $indexConfig) {
$indexConfig['index']->create($indexConfig['config'], true);
}
}
}

79
Resetter.php Normal file
View file

@ -0,0 +1,79 @@
<?php
namespace FOQ\ElasticaBundle;
/**
* Deletes and recreates indexes
*/
class Resetter
{
protected $indexConfigsByName;
/**
* Constructor.
*
* @param array $indexConfigsByName
*/
public function __construct(array $indexConfigsByName)
{
$this->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];
}
}

View file

@ -12,6 +12,7 @@
<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.elastica_to_model_transformer.collection.class">FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
<parameter key="foq_elastica.provider_registry.class">FOQ\ElasticaBundle\Provider\ProviderRegistry</parameter>
</parameters>
<services>
@ -37,11 +38,7 @@
<argument /> <!-- default index -->
</service>
<service id="foq_elastica.populator" class="FOQ\ElasticaBundle\Populator">
<argument /> <!-- providers -->
</service>
<service id="foq_elastica.reseter" class="FOQ\ElasticaBundle\Reseter">
<service id="foq_elastica.resetter" class="FOQ\ElasticaBundle\Resetter">
<argument /> <!-- index configs -->
</service>
@ -66,6 +63,11 @@
<argument type="collection" /> <!-- options -->
</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>
</container>

View file

@ -6,12 +6,11 @@
<services>
<service id="foq_elastica.provider.prototype.mongodb" class="FOQ\ElasticaBundle\Doctrine\MongoDB\Provider" public="false" abstract="true">
<argument /> <!-- type -->
<argument type="service" id="doctrine.odm.mongodb" />
<service id="foq_elastica.provider.prototype.mongodb" class="FOQ\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine.odm.mongodb" />
</service>
<service id="foq_elastica.listener.prototype.mongodb" class="FOQ\ElasticaBundle\Doctrine\MongoDB\Listener" public="false" abstract="true">

View file

@ -6,12 +6,11 @@
<services>
<service id="foq_elastica.provider.prototype.orm" class="FOQ\ElasticaBundle\Doctrine\ORM\Provider" public="false" abstract="true">
<argument /> <!-- type -->
<argument type="service" id="doctrine" />
<service id="foq_elastica.provider.prototype.orm" class="FOQ\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine" />
</service>
<service id="foq_elastica.listener.prototype.orm" class="FOQ\ElasticaBundle\Doctrine\ORM\Listener" public="false" abstract="true">

View file

@ -5,8 +5,7 @@
<services>
<service id="foq_elastica.provider.prototype.propel" class="FOQ\ElasticaBundle\Propel\Provider" public="false" abstract="true">
<argument /> <!-- type -->
<service id="foq_elastica.provider.prototype.propel" class="FOQ\ElasticaBundle\Propel\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->

View file

@ -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;
}
}

View file

@ -0,0 +1,183 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Doctrine;
class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
private $objectClass;
private $objectManager;
private $objectPersister;
private $options;
private $managerRegistry;
public function setUp()
{
if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
$this->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();
}

View file

@ -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());
}
}

View file

@ -1,91 +0,0 @@
<?php
namespace FOQ\ElasticaBundle\Tests\MappingRegistry;
use FOQ\ElasticaBundle\MappingRegistry;
use Elastica_Type;
use Elastica_Index;
class MappingRegistryTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
if (!class_exists('Elastica_Type') || !class_exists('Elastica_Index')) {
$this->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;
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Populator;
use FOQ\ElasticaBundle\Populator;
use FOQ\ElasticaBundle\Provider\ProviderInterface;
use Closure;
class PopulatorMock extends Populator
{
public $providers = array();
}
class PopulatorTest extends \PHPUnit_Framework_TestCase
{
public function testThatWeCanAddProvider()
{
$provider = $this->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; });
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Provider;
use FOQ\ElasticaBundle\Provider\ProviderRegistry;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ProviderRegistryTest extends \PHPUnit_Framework_TestCase
{
private $container;
private $registry;
public function setUp()
{
$this->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');
}
}

View file

@ -1,71 +0,0 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Reseter;
use FOQ\ElasticaBundle\Reseter;
use FOQ\ElasticaBundle\IndexManager;
use Elastica_Exception_Response;
use Elastica_Response;
class Index
{
public $deleted = false;
public $created = false;
public function delete()
{
$this->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);
}
}

138
Tests/ResetterTest.php Normal file
View file

@ -0,0 +1,138 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Resetter;
use FOQ\ElasticaBundle\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase
{
private $indexConfigsByName;
public function setUp()
{
$this->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();
}
}

View file

@ -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": "" }