diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md
index 478840e..10d7887 100644
--- a/CHANGELOG-3.1.md
+++ b/CHANGELOG-3.1.md
@@ -44,3 +44,4 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
* New events `PRE_INDEX_RESET`, `POST_INDEX_RESET`, `PRE_TYPE_RESET` and
`POST_TYPE_RESET` are run before and after operations that will reset an
index. #744
+ * Added indexable callback support for the __invoke method of a service. #823
diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php
index e3d2918..08b501a 100644
--- a/Command/PopulateCommand.php
+++ b/Command/PopulateCommand.php
@@ -90,8 +90,12 @@ class PopulateCommand extends ContainerAwareCommand
$index = $input->getOption('index');
$type = $input->getOption('type');
$reset = !$input->getOption('no-reset');
- $options = $input->getOptions();
- $options['ignore-errors'] = $input->getOption('ignore-errors');
+ $options = array(
+ 'batch_size' => $input->getOption('batch-size'),
+ 'ignore_errors' => $input->getOption('ignore-errors'),
+ 'offset' => $input->getOption('offset'),
+ 'sleep' => $input->getOption('sleep')
+ );
if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */
diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php
index 9845edc..ec198a8 100644
--- a/Doctrine/AbstractProvider.php
+++ b/Doctrine/AbstractProvider.php
@@ -7,6 +7,7 @@ use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
use FOS\ElasticaBundle\Provider\IndexableInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class AbstractProvider extends BaseAbstractProvider
{
@@ -26,7 +27,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
- * @param array $options
+ * @param array $baseOptions
* @param ManagerRegistry $managerRegistry
* @param SliceFetcherInterface $sliceFetcher
*/
@@ -34,71 +35,106 @@ abstract class AbstractProvider extends BaseAbstractProvider
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
- array $options,
+ array $baseOptions,
ManagerRegistry $managerRegistry,
SliceFetcherInterface $sliceFetcher = null
) {
- parent::__construct($objectPersister, $indexable, $objectClass, array_merge(array(
- 'clear_object_manager' => true,
- 'debug_logging' => false,
- 'ignore_errors' => false,
- 'query_builder_method' => 'createQueryBuilder',
- ), $options));
+ parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions);
$this->managerRegistry = $managerRegistry;
$this->sliceFetcher = $sliceFetcher;
}
+ /**
+ * Counts objects that would be indexed using the query builder.
+ *
+ * @param object $queryBuilder
+ *
+ * @return integer
+ */
+ abstract protected function countObjects($queryBuilder);
+
+ /**
+ * Creates the query builder, which will be used to fetch objects to index.
+ *
+ * @param string $method
+ *
+ * @return object
+ */
+ abstract protected function createQueryBuilder($method);
+
+ /**
+ * Fetches a slice of objects using the query builder.
+ *
+ * @param object $queryBuilder
+ * @param integer $limit
+ * @param integer $offset
+ *
+ * @return array
+ */
+ abstract protected function fetchSlice($queryBuilder, $limit, $offset);
+
/**
* {@inheritDoc}
*/
- public function populate(\Closure $loggerClosure = null, array $options = array())
+ protected function doPopulate($options, \Closure $loggerClosure = null)
{
- if (!$this->options['debug_logging']) {
- $logger = $this->disableLogging();
- }
-
- $queryBuilder = $this->createQueryBuilder();
- $nbObjects = $this->countObjects($queryBuilder);
- $offset = isset($options['offset']) ? intval($options['offset']) : 0;
- $sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
- $batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
- $ignoreErrors = isset($options['ignore-errors']) ? $options['ignore-errors'] : $this->options['ignore_errors'];
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
- $objects = array();
- for (; $offset < $nbObjects; $offset += $batchSize) {
- $objects = $this->getSlice($queryBuilder, $batchSize, $offset, $objects);
- $objects = array_filter($objects, array($this, 'isObjectIndexable'));
+ $queryBuilder = $this->createQueryBuilder($options['query_builder_method']);
+ $nbObjects = $this->countObjects($queryBuilder);
+ $offset = $options['offset'];
- if (!empty($objects)) {
- if (!$ignoreErrors) {
+ $objects = array();
+ for (; $offset < $nbObjects; $offset += $options['batch_size']) {
+ try {
+ $objects = $this->getSlice($queryBuilder, $options['batch_size'], $offset, $objects);
+ $objects = $this->filterObjects($options, $objects);
+
+ if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
- } else {
- try {
- $this->objectPersister->insertMany($objects);
- } catch (BulkResponseException $e) {
- if ($loggerClosure) {
- $loggerClosure($batchSize, $nbObjects, sprintf('%s', $e->getMessage()));
- }
- }
+ }
+ } catch (BulkResponseException $e) {
+ if (!$options['ignore_errors']) {
+ throw $e;
+ }
+
+ if (null !== $loggerClosure) {
+ $loggerClosure(
+ $options['batch_size'],
+ $nbObjects,
+ sprintf('%s', $e->getMessage())
+ );
}
}
- if ($this->options['clear_object_manager']) {
+ if ($options['clear_object_manager']) {
$manager->clear();
}
- usleep($sleep);
+ usleep($options['sleep']);
- if ($loggerClosure) {
- $loggerClosure($batchSize, $nbObjects);
+ if (null !== $loggerClosure) {
+ $loggerClosure($options['batch_size'], $nbObjects);
}
}
+ }
- if (!$this->options['debug_logging']) {
- $this->enableLogging($logger);
- }
+ /**
+ * {@inheritDoc}
+ */
+ protected function configureOptions()
+ {
+ parent::configureOptions();
+
+ $this->resolver->setDefaults(array(
+ 'clear_object_manager' => true,
+ 'debug_logging' => false,
+ 'ignore_errors' => false,
+ 'offset' => 0,
+ 'query_builder_method' => 'createQueryBuilder',
+ 'sleep' => 0
+ ));
}
/**
@@ -112,7 +148,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
*
* @return array
*/
- protected function getSlice($queryBuilder, $limit, $offset, $lastSlice)
+ private function getSlice($queryBuilder, $limit, $offset, $lastSlice)
{
if (!$this->sliceFetcher) {
return $this->fetchSlice($queryBuilder, $limit, $offset);
@@ -131,47 +167,4 @@ abstract class AbstractProvider extends BaseAbstractProvider
$identifierFieldNames
);
}
-
- /**
- * Counts objects that would be indexed using the query builder.
- *
- * @param object $queryBuilder
- *
- * @return integer
- */
- abstract protected function countObjects($queryBuilder);
-
- /**
- * Disables logging and returns the logger that was previously set.
- *
- * @return mixed
- */
- abstract protected function disableLogging();
-
- /**
- * Reenables the logger with the previously returned logger from disableLogging();.
- *
- * @param mixed $logger
- *
- * @return mixed
- */
- abstract protected function enableLogging($logger);
-
- /**
- * Fetches a slice of objects using the query builder.
- *
- * @param object $queryBuilder
- * @param integer $limit
- * @param integer $offset
- *
- * @return array
- */
- abstract protected function fetchSlice($queryBuilder, $limit, $offset);
-
- /**
- * Creates the query builder, which will be used to fetch objects to index.
- *
- * @return object
- */
- abstract protected function createQueryBuilder();
}
diff --git a/Doctrine/MongoDB/Provider.php b/Doctrine/MongoDB/Provider.php
index 6b2bd2c..e4b08c5 100644
--- a/Doctrine/MongoDB/Provider.php
+++ b/Doctrine/MongoDB/Provider.php
@@ -44,7 +44,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
+ * {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@@ -58,7 +58,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
+ * {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@@ -75,13 +75,13 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
+ * {@inheritDoc}
*/
- protected function createQueryBuilder()
+ protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
- ->{$this->options['query_builder_method']}();
+ ->{$method}();
}
}
diff --git a/Doctrine/ORM/Provider.php b/Doctrine/ORM/Provider.php
index fc59667..303242a 100644
--- a/Doctrine/ORM/Provider.php
+++ b/Doctrine/ORM/Provider.php
@@ -46,7 +46,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
+ * {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@@ -69,7 +69,9 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
+ * This method should remain in sync with SliceFetcher::fetch until it is deprecated and removed.
+ *
+ * {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@@ -103,14 +105,14 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
+ * {@inheritDoc}
*/
- protected function createQueryBuilder()
+ protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
// ORM query builders require an alias argument
- ->{$this->options['query_builder_method']}(static::ENTITY_ALIAS);
+ ->{$method}(static::ENTITY_ALIAS);
}
}
diff --git a/Doctrine/ORM/SliceFetcher.php b/Doctrine/ORM/SliceFetcher.php
index 86ad1b4..ac6c816 100644
--- a/Doctrine/ORM/SliceFetcher.php
+++ b/Doctrine/ORM/SliceFetcher.php
@@ -14,6 +14,9 @@ use FOS\ElasticaBundle\Doctrine\SliceFetcherInterface;
class SliceFetcher implements SliceFetcherInterface
{
/**
+ * This method should remain in sync with Provider::fetchSlice until that method is deprecated and
+ * removed.
+ *
* {@inheritdoc}
*/
public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames)
diff --git a/Exception/AliasIsIndexException.php b/Exception/AliasIsIndexException.php
index 1df7a62..9af6ee3 100644
--- a/Exception/AliasIsIndexException.php
+++ b/Exception/AliasIsIndexException.php
@@ -6,6 +6,6 @@ class AliasIsIndexException extends \Exception
{
public function __construct($indexName)
{
- parent::__construct(sprintf('Expected alias %s instead of index', $indexName));
+ parent::__construct(sprintf('Expected %s to be an alias but it is an index.', $indexName));
}
}
diff --git a/Index/AliasProcessor.php b/Index/AliasProcessor.php
index c4f1464..d8f608d 100644
--- a/Index/AliasProcessor.php
+++ b/Index/AliasProcessor.php
@@ -54,37 +54,47 @@ class AliasProcessor
$client = $index->getClient();
$aliasName = $indexConfig->getElasticSearchName();
- $oldIndexName = false;
+ $oldIndexName = null;
$newIndexName = $index->getName();
try {
- $aliasedIndexes = $this->getAliasedIndexes($client, $aliasName);
+ $oldIndexName = $this->getAliasedIndex($client, $aliasName);
} catch (AliasIsIndexException $e) {
if (!$force) {
throw $e;
}
$this->deleteIndex($client, $aliasName);
- $aliasedIndexes = array();
}
- if (count($aliasedIndexes) > 1) {
- throw new \RuntimeException(
- sprintf(
- 'Alias %s is used for multiple indexes: [%s].
- Make sure it\'s either not used or is assigned to one index only',
- $aliasName,
- implode(', ', $aliasedIndexes)
- )
- );
+ try {
+ $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
+ $client->request('_aliases', 'POST', $aliasUpdateRequest);
+ } catch (ExceptionInterface $e) {
+ $this->cleanupRenameFailure($client, $newIndexName, $e);
}
+ // Delete the old index after the alias has been switched
+ if (null !== $oldIndexName) {
+ $this->deleteIndex($client, $oldIndexName);
+ }
+ }
+
+ /**
+ * Builds an ElasticSearch request to rename or create an alias.
+ *
+ * @param string|null $aliasedIndex
+ * @param string $aliasName
+ * @param string $newIndexName
+ * @return array
+ */
+ private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
+ {
$aliasUpdateRequest = array('actions' => array());
- if (count($aliasedIndexes) === 1) {
+ if (null !== $aliasedIndex) {
// if the alias is set - add an action to remove it
- $oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
- 'remove' => array('index' => $oldIndexName, 'alias' => $aliasName),
+ 'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
);
}
@@ -93,58 +103,68 @@ class AliasProcessor
'add' => array('index' => $newIndexName, 'alias' => $aliasName),
);
- try {
- $client->request('_aliases', 'POST', $aliasUpdateRequest);
- } catch (ExceptionInterface $renameAliasException) {
- $additionalError = '';
- // if we failed to move the alias, delete the newly built index
- try {
- $index->delete();
- } catch (ExceptionInterface $deleteNewIndexException) {
- $additionalError = sprintf(
- 'Tried to delete newly built index %s, but also failed: %s',
- $newIndexName,
- $deleteNewIndexException->getMessage()
- );
- }
+ return $aliasUpdateRequest;
+ }
- throw new \RuntimeException(
- sprintf(
- 'Failed to updated index alias: %s. %s',
- $renameAliasException->getMessage(),
- $additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
- ), 0, $renameAliasException
+ /**
+ * Cleans up an index when we encounter a failure to rename the alias.
+ *
+ * @param Client $client
+ * @param string $indexName
+ * @param \Exception $renameAliasException
+ */
+ private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
+ {
+ $additionalError = '';
+ try {
+ $this->deleteIndex($client, $indexName);
+ } catch (ExceptionInterface $deleteNewIndexException) {
+ $additionalError = sprintf(
+ 'Tried to delete newly built index %s, but also failed: %s',
+ $indexName,
+ $deleteNewIndexException->getMessage()
);
}
- // Delete the old index after the alias has been switched
- if ($oldIndexName) {
- $oldIndex = new Index($client, $oldIndexName);
- try {
- $oldIndex->delete();
- } catch (ExceptionInterface $deleteOldIndexException) {
- throw new \RuntimeException(
- sprintf(
- 'Failed to delete old index %s with message: %s',
- $oldIndexName,
- $deleteOldIndexException->getMessage()
- ), 0, $deleteOldIndexException
- );
- }
+ throw new \RuntimeException(sprintf(
+ 'Failed to updated index alias: %s. %s',
+ $renameAliasException->getMessage(),
+ $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
+ ), 0, $renameAliasException);
+ }
+
+ /**
+ * Delete an index.
+ *
+ * @param Client $client
+ * @param string $indexName Index name to delete
+ */
+ private function deleteIndex(Client $client, $indexName)
+ {
+ try {
+ $path = sprintf("%s", $indexName);
+ $client->request($path, Request::DELETE);
+ } catch (ExceptionInterface $deleteOldIndexException) {
+ throw new \RuntimeException(sprintf(
+ 'Failed to delete index %s with message: %s',
+ $indexName,
+ $deleteOldIndexException->getMessage()
+ ), 0, $deleteOldIndexException);
}
}
/**
- * Returns array of indexes which are mapped to given alias.
+ * Returns the name of a single index that an alias points to or throws
+ * an exception if there is more than one.
*
* @param Client $client
* @param string $aliasName Alias name
*
- * @return array
+ * @return string|null
*
* @throws AliasIsIndexException
*/
- private function getAliasedIndexes(Client $client, $aliasName)
+ private function getAliasedIndex(Client $client, $aliasName)
{
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
@@ -163,18 +183,15 @@ class AliasProcessor
}
}
- return $aliasedIndexes;
- }
+ if (count($aliasedIndexes) > 1) {
+ throw new \RuntimeException(sprintf(
+ 'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
+ 'either not used or is assigned to one index only',
+ $aliasName,
+ implode(', ', $aliasedIndexes)
+ ));
+ }
- /**
- * Delete an index.
- *
- * @param Client $client
- * @param string $indexName Index name to delete
- */
- private function deleteIndex(Client $client, $indexName)
- {
- $path = sprintf("%s", $indexName);
- $client->request($path, Request::DELETE);
+ return array_shift($aliasedIndexes);
}
}
diff --git a/Index/Resetter.php b/Index/Resetter.php
index e144a62..2b39157 100644
--- a/Index/Resetter.php
+++ b/Index/Resetter.php
@@ -86,12 +86,12 @@ class Resetter
*/
public function resetIndex($indexName, $populating = false, $force = false)
{
- $event = new IndexResetEvent($indexName, $populating, $force);
- $this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
-
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
$index = $this->indexManager->getIndex($indexName);
+ $event = new IndexResetEvent($indexName, $populating, $force);
+ $this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
+
if ($indexConfig->isUseAlias()) {
$this->aliasProcessor->setRootName($indexConfig, $index);
}
@@ -117,12 +117,12 @@ class Resetter
*/
public function resetIndexType($indexName, $typeName)
{
- $event = new TypeResetEvent($indexName, $typeName);
- $this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
-
$typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName);
$type = $this->indexManager->getIndex($indexName)->getType($typeName);
+ $event = new TypeResetEvent($indexName, $typeName);
+ $this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
+
try {
$type->delete();
} catch (ResponseException $e) {
diff --git a/Propel/Provider.php b/Propel/Provider.php
index f28faa4..57d7176 100644
--- a/Propel/Provider.php
+++ b/Propel/Provider.php
@@ -14,31 +14,43 @@ class Provider extends AbstractProvider
/**
* {@inheritDoc}
*/
- public function populate(\Closure $loggerClosure = null, array $options = array())
+ public function doPopulate($options, \Closure $loggerClosure = null)
{
$queryClass = $this->objectClass.'Query';
$nbObjects = $queryClass::create()->count();
- $offset = isset($options['offset']) ? intval($options['offset']) : 0;
- $sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
- $batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
- for (; $offset < $nbObjects; $offset += $batchSize) {
+ $offset = $options['offset'];
+
+ for (; $offset < $nbObjects; $offset += $options['batch_size']) {
$objects = $queryClass::create()
- ->limit($batchSize)
+ ->limit($options['batch_size'])
->offset($offset)
->find()
->getArrayCopy();
-
- $objects = array_filter($objects, array($this, 'isObjectIndexable'));
+ $objects = $this->filterObjects($options, $objects);
if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
}
- usleep($sleep);
+ usleep($options['sleep']);
if ($loggerClosure) {
- $loggerClosure($batchSize, $nbObjects);
+ $loggerClosure($options['batch_size'], $nbObjects);
}
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function disableLogging()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function enableLogging($logger)
+ {
+ }
}
diff --git a/Provider/AbstractProvider.php b/Provider/AbstractProvider.php
index a743d17..87614cd 100644
--- a/Provider/AbstractProvider.php
+++ b/Provider/AbstractProvider.php
@@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* AbstractProvider.
@@ -10,9 +11,9 @@ use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
abstract class AbstractProvider implements ProviderInterface
{
/**
- * @var ObjectPersisterInterface
+ * @var array
*/
- protected $objectPersister;
+ protected $baseOptions;
/**
* @var string
@@ -20,9 +21,14 @@ abstract class AbstractProvider implements ProviderInterface
protected $objectClass;
/**
- * @var array
+ * @var ObjectPersisterInterface
*/
- protected $options;
+ protected $objectPersister;
+
+ /**
+ * @var OptionsResolver
+ */
+ protected $resolver;
/**
* @var IndexableInterface
@@ -35,26 +41,114 @@ abstract class AbstractProvider implements ProviderInterface
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
- * @param array $options
+ * @param array $baseOptions
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
- array $options = array()
+ array $baseOptions = array()
) {
+ $this->baseOptions = $baseOptions;
$this->indexable = $indexable;
$this->objectClass = $objectClass;
$this->objectPersister = $objectPersister;
+ $this->resolver = new OptionsResolver();
+ $this->configureOptions();
+ }
- $this->options = array_merge(array(
+ /**
+ * {@inheritDoc}
+ */
+ public function populate(\Closure $loggerClosure = null, array $options = array())
+ {
+ $options = $this->resolveOptions($options);
+
+ $logger = !$options['debug_logging'] ?
+ $this->disableLogging() :
+ null;
+
+ $this->doPopulate($options, $loggerClosure);
+
+ if (null !== $logger) {
+ $this->enableLogging($logger);
+ }
+ }
+
+ /**
+ * Disables logging and returns the logger that was previously set.
+ *
+ * @return mixed
+ */
+ abstract protected function disableLogging();
+
+ /**
+ * Perform actual population.
+ *
+ * @param array $options
+ * @param \Closure $loggerClosure
+ */
+ abstract protected function doPopulate($options, \Closure $loggerClosure = null);
+
+ /**
+ * Reenables the logger with the previously returned logger from disableLogging();.
+ *
+ * @param mixed $logger
+ *
+ * @return mixed
+ */
+ abstract protected function enableLogging($logger);
+
+ /**
+ * Configures the option resolver.
+ */
+ protected function configureOptions()
+ {
+ $this->resolver->setDefaults(array(
'batch_size' => 100,
- ), $options);
+ 'skip_indexable_check' => false,
+ ));
+
+ $this->resolver->setRequired(array(
+ 'indexName',
+ 'typeName',
+ ));
+ }
+
+
+ /**
+ * Filters objects away if they are not indexable.
+ *
+ * @param array $options
+ * @param array $objects
+ * @return array
+ */
+ protected function filterObjects(array $options, array $objects)
+ {
+ if ($options['skip_indexable_check']) {
+ return $objects;
+ }
+
+ $index = $options['indexName'];
+ $type = $options['typeName'];
+
+ $return = array();
+ foreach ($objects as $object) {
+ if (!$this->indexable->isObjectIndexable($index, $type, $object)) {
+ continue;
+ }
+
+ $return[] = $object;
+ }
+
+ return $return;
}
/**
* Checks if a given object should be indexed or not.
*
+ * @deprecated To be removed in 4.0
+ *
* @param object $object
*
* @return bool
@@ -62,8 +156,8 @@ abstract class AbstractProvider implements ProviderInterface
protected function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
- $this->options['indexName'],
- $this->options['typeName'],
+ $this->baseOptions['indexName'],
+ $this->baseOptions['typeName'],
$object
);
}
@@ -82,4 +176,17 @@ abstract class AbstractProvider implements ProviderInterface
return sprintf('(RAM : current=%uMo peak=%uMo)', $memory, $memoryMax);
}
+
+ /**
+ * Merges the base options provided by the class with options passed to the populate
+ * method and runs them through the resolver.
+ *
+ * @param array $options
+ *
+ * @return array
+ */
+ protected function resolveOptions(array $options)
+ {
+ return $this->resolver->resolve(array_merge($this->baseOptions, $options));
+ }
}
diff --git a/Provider/Indexable.php b/Provider/Indexable.php
index c72c9b9..c26da5a 100644
--- a/Provider/Indexable.php
+++ b/Provider/Indexable.php
@@ -55,6 +55,7 @@ class Indexable implements IndexableInterface
/**
* @param array $callbacks
+ * @param ContainerInterface $container
*/
public function __construct(array $callbacks, ContainerInterface $container)
{
@@ -81,7 +82,7 @@ class Indexable implements IndexableInterface
}
if ($callback instanceof Expression) {
- return $this->getExpressionLanguage()->evaluate($callback, array(
+ return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
$this->getExpressionVar($object) => $object,
));
@@ -112,39 +113,48 @@ class Indexable implements IndexableInterface
return $callback;
}
- if (is_array($callback)) {
- list($class, $method) = $callback + array(null, null);
-
- if (is_object($class)) {
- $class = get_class($class);
- }
-
- if (strpos($class, '@') === 0) {
- $service = $this->container->get(substr($class, 1));
-
- return array($service, $method);
- }
-
- if ($class && $method) {
- throw new \InvalidArgumentException(sprintf('Callback for type "%s", "%s::%s()", is not callable.', $type, $class, $method));
- }
+ if (is_array($callback) && !is_object($callback[0])) {
+ return $this->processArrayToCallback($type, $callback);
}
- if (is_string($callback) && $expression = $this->getExpressionLanguage()) {
- $callback = new Expression($callback);
-
- try {
- $expression->compile($callback, array('object', $this->getExpressionVar($object)));
-
- return $callback;
- } catch (SyntaxError $e) {
- throw new \InvalidArgumentException(sprintf('Callback for type "%s" is an invalid expression', $type), $e->getCode(), $e);
- }
+ if (is_string($callback)) {
+ return $this->buildExpressionCallback($type, $object, $callback);
}
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
}
+ /**
+ * Processes a string expression into an Expression.
+ *
+ * @param string $type
+ * @param mixed $object
+ * @param string $callback
+ *
+ * @return Expression
+ */
+ private function buildExpressionCallback($type, $object, $callback)
+ {
+ $expression = $this->getExpressionLanguage();
+ if (!$expression) {
+ throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
+ }
+
+ try {
+ $callback = new Expression($callback);
+ $expression->compile($callback, array(
+ 'object', $this->getExpressionVar($object)
+ ));
+
+ return $callback;
+ } catch (SyntaxError $e) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Callback for type "%s" is an invalid expression',
+ $type
+ ), $e->getCode(), $e);
+ }
+ }
+
/**
* Retreives a cached callback, or creates a new callback if one is not found.
*
@@ -163,15 +173,13 @@ class Indexable implements IndexableInterface
}
/**
- * @return bool|ExpressionLanguage
+ * Returns the ExpressionLanguage class if it is available.
+ *
+ * @return ExpressionLanguage|null
*/
private function getExpressionLanguage()
{
- if (null === $this->expressionLanguage) {
- if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
- return false;
- }
-
+ if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$this->expressionLanguage = new ExpressionLanguage();
}
@@ -179,14 +187,54 @@ class Indexable implements IndexableInterface
}
/**
+ * Returns the variable name to be used to access the object when using the ExpressionLanguage
+ * component to parse and evaluate an expression.
+ *
* @param mixed $object
*
* @return string
*/
private function getExpressionVar($object = null)
{
+ if (!is_object($object)) {
+ return 'object';
+ }
+
$ref = new \ReflectionClass($object);
return strtolower($ref->getShortName());
}
+
+ /**
+ * Processes an array into a callback. Replaces the first element with a service if
+ * it begins with an @.
+ *
+ * @param string $type
+ * @param array $callback
+ * @return array
+ */
+ private function processArrayToCallback($type, array $callback)
+ {
+ list($class, $method) = $callback + array(null, '__invoke');
+
+ if (strpos($class, '@') === 0) {
+ $service = $this->container->get(substr($class, 1));
+ $callback = array($service, $method);
+
+ if (!is_callable($callback)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Method "%s" on service "%s" is not callable.',
+ $method,
+ substr($class, 1)
+ ));
+ }
+
+ return $callback;
+ }
+
+ throw new \InvalidArgumentException(sprintf(
+ 'Unable to parse callback array for type "%s"',
+ $type
+ ));
+ }
}
diff --git a/Resources/doc/types.md b/Resources/doc/types.md
index 2d575cd..0e82c60 100644
--- a/Resources/doc/types.md
+++ b/Resources/doc/types.md
@@ -201,13 +201,18 @@ index enabled users.
The callback option supports multiple approaches:
* A method on the object itself provided as a string. `enabled` will call
- `Object->enabled()`
+ `Object->enabled()`. Note that this does not support chaining methods with dot notation
+ like property paths. To achieve something similar use the ExpressionLanguage option
+ below.
* An array of a service id and a method which will be called with the object as the first
and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
method on a service defined as my_custom_service.
* An array of a class and a static method to call on that class which will be called with
the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
will call Acme\DemoBundle\IndexableChecker::isIndexable($object)
+* A single element array with a service id can be used if the service has an __invoke
+ method. Such an invoke method must accept a single parameter for the object to be indexed.
+ `[ @my_custom_invokable_service ]`
* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
expression provided as a string. The object being indexed will be supplied as `object`
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more
diff --git a/Tests/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php
index 7b41837..aa28a4c 100644
--- a/Tests/Doctrine/AbstractProviderTest.php
+++ b/Tests/Doctrine/AbstractProviderTest.php
@@ -252,7 +252,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('Elastica\Exception\Bulk\ResponseException');
- $provider->populate(null, array('ignore-errors' => false));
+ $provider->populate(null, array('ignore_errors' => false));
}
public function testPopulateRunsIndexCallable()
@@ -280,7 +280,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->objectPersister->expects($this->once())
->method('insertMany')
- ->with(array(1 => 2));
+ ->with(array(2));
$provider->populate();
}
diff --git a/Tests/Index/AliasProcessorTest.php b/Tests/Index/AliasProcessorTest.php
new file mode 100644
index 0000000..f1592b2
--- /dev/null
+++ b/Tests/Index/AliasProcessorTest.php
@@ -0,0 +1,223 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Index;
+
+use Elastica\Exception\ResponseException;
+use Elastica\Request;
+use Elastica\Response;
+use FOS\ElasticaBundle\Configuration\IndexConfig;
+use FOS\ElasticaBundle\Index\AliasProcessor;
+
+class AliasProcessorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var AliasProcessor
+ */
+ private $processor;
+
+ /**
+ * @dataProvider getSetRootNameData
+ * @param string $name
+ * @param array $configArray
+ * @param string $resultStartsWith
+ */
+ public function testSetRootName($name, $configArray, $resultStartsWith)
+ {
+ $indexConfig = new IndexConfig($name, array(), $configArray);
+ $index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $index->expects($this->once())
+ ->method('overrideName')
+ ->with($this->stringStartsWith($resultStartsWith));
+
+ $this->processor->setRootName($indexConfig, $index);
+ }
+
+ public function testSwitchAliasNoAliasSet()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array()));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ public function testSwitchAliasExistingAliasSet()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name'))
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testSwitchAliasThrowsWhenMoreThanOneExists()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ 'another_old_unique_name' => array('aliases' => array('name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ /**
+ * @expectedException \FOS\ElasticaBundle\Exception\AliasIsIndexException
+ */
+ public function testSwitchAliasThrowsWhenAliasIsAnIndex()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'name' => array(),
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ public function testSwitchAliasDeletesIndexCollisionIfForced()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'name' => array(),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('name', 'DELETE');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function testSwitchAliasDeletesOldIndex()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+ $client->expects($this->at(2))
+ ->method('request')
+ ->with('old_unique_name', 'DELETE');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function testSwitchAliasCleansUpOnRenameFailure()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )))
+ ->will($this->throwException(new ResponseException(new Request(''), new Response(''))));
+ $client->expects($this->at(2))
+ ->method('request')
+ ->with('unique_name', 'DELETE');
+ // Not an annotation: we do not want a RuntimeException until now.
+ $this->setExpectedException('RuntimeException');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function getSetRootNameData()
+ {
+ return array(
+ array('name', array(), 'name_'),
+ array('name', array('elasticSearchName' => 'notname'), 'notname_')
+ );
+ }
+
+ protected function setUp()
+ {
+ $this->processor = new AliasProcessor();
+ }
+
+ private function getMockedIndex($name)
+ {
+ $index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $client = $this->getMockBuilder('Elastica\\Client')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $index->expects($this->any())
+ ->method('getClient')
+ ->willReturn($client);
+
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn($name);
+
+ return array($index, $client);
+ }
+}
diff --git a/Tests/Index/ResetterTest.php b/Tests/Index/ResetterTest.php
index 35a0bd9..9b4cd05 100644
--- a/Tests/Index/ResetterTest.php
+++ b/Tests/Index/ResetterTest.php
@@ -5,8 +5,14 @@ namespace FOS\ElasticaBundle\Tests\Index;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
+use Elastica\Type;
use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\IndexConfig;
+use FOS\ElasticaBundle\Configuration\TypeConfig;
+use FOS\ElasticaBundle\Elastica\Index;
+use FOS\ElasticaBundle\Event\IndexResetEvent;
+use FOS\ElasticaBundle\Event\TypeResetEvent;
+use FOS\ElasticaBundle\Index\AliasProcessor;
use FOS\ElasticaBundle\Index\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase
@@ -16,230 +22,253 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
*/
private $resetter;
- private $configManager;
- private $indexManager;
private $aliasProcessor;
+ private $configManager;
+ private $dispatcher;
+ private $elasticaClient;
+ private $indexManager;
private $mappingBuilder;
- public function setUp()
- {
- $this->markTestIncomplete('To be rewritten');
- $this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
- ->disableOriginalConstructor()
- ->getMock();
- $this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
- ->disableOriginalConstructor()
- ->getMock();
- $this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
- ->disableOriginalConstructor()
- ->getMock();
- $this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder')
- ->disableOriginalConstructor()
- ->getMock();
- $this->resetter = $this->getMockBuilder('FOS\ElasticaBundle\Index\Resetter')
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->resetter = new Resetter($this->configManager, $this->indexManager, $this->aliasProcessor, $this->mappingBuilder, $this->resetter);
-
- /*$this->indexConfigsByName = array(
- 'foo' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array(
- 'dynamic_templates' => array(),
- 'properties' => array(),
- ),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- 'bar' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array('properties' => array()),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- 'parent' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array(
- 'properties' => array(
- 'field_2' => array()
- ),
- '_parent' => array(
- 'type' => 'b',
- 'property' => 'b',
- 'identifier' => 'id'
- ),
- ),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- );*/
- }
-
public function testResetAllIndexes()
{
+ $indexName = 'index1';
+ $indexConfig = new IndexConfig($indexName, array(), array());
+ $this->mockIndex($indexName, $indexConfig);
+
$this->configManager->expects($this->once())
->method('getIndexNames')
- ->will($this->returnValue(array('index1')));
+ ->will($this->returnValue(array($indexName)));
- $this->configManager->expects($this->once())
- ->method('getIndexConfiguration')
- ->with('index1')
- ->will($this->returnValue(new IndexConfig('index1', array(), array())));
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
- $this->indexManager->expects($this->once())
- ->method('getIndex')
- ->with('index1')
- ->will($this->returnValue());
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
- /*$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);*/
$this->resetter->resetAllIndexes();
}
public function testResetIndex()
{
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('create')
- ->with($this->indexConfigsByName['foo']['config'], true);
+ $indexConfig = new IndexConfig('index1', array(), array());
+ $this->mockIndex('index1', $indexConfig);
- $this->indexConfigsByName['bar']['index']->expects($this->never())
- ->method('create');
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndex('foo');
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
+ }
+
+ public function testResetIndexWithDifferentName()
+ {
+ $indexConfig = new IndexConfig('index1', array(), array(
+ 'elasticSearchName' => 'notIndex1'
+ ));
+ $this->mockIndex('index1', $indexConfig);
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
+ }
+
+ public function testResetIndexWithDifferentNameAndAlias()
+ {
+ $indexConfig = new IndexConfig('index1', array(), array(
+ 'elasticSearchName' => 'notIndex1',
+ 'useAlias' => true
+ ));
+ $index = $this->mockIndex('index1', $indexConfig);
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->aliasProcessor->expects($this->once())
+ ->method('switchIndexAlias')
+ ->with($indexConfig, $index, false);
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
}
/**
* @expectedException \InvalidArgumentException
*/
- public function testResetIndexShouldThrowExceptionForInvalidIndex()
+ public function testFailureWhenMissingIndexDoesntDispatch()
{
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndex('baz');
+ $this->configManager->expects($this->once())
+ ->method('getIndexConfiguration')
+ ->with('nonExistant')
+ ->will($this->throwException(new \InvalidArgumentException));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+
+ $this->resetter->resetIndex('nonExistant');
}
- public function testResetIndexType()
+ public function testResetType()
{
- $type = $this->getMockElasticaType();
+ $typeConfig = new TypeConfig('type', array(), array());
+ $this->mockType('type', 'index', $typeConfig);
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
+ $this->dispatcherExpects(array(
+ array(TypeResetEvent::PRE_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent')),
+ array(TypeResetEvent::POST_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent'))
+ ));
- $type->expects($this->once())
- ->method('delete');
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index/type/', 'DELETE'),
+ array('index/type/_mapping', 'PUT', array('type' => array()), array())
+ );
- $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
- $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'a');
+ $this->resetter->resetIndexType('index', 'type');
}
/**
* @expectedException \InvalidArgumentException
*/
- public function testResetIndexTypeShouldThrowExceptionForInvalidIndex()
+ public function testNonExistantResetType()
{
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('baz', 'a');
+ $this->configManager->expects($this->once())
+ ->method('getTypeConfiguration')
+ ->with('index', 'nonExistant')
+ ->will($this->throwException(new \InvalidArgumentException));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+
+ $this->resetter->resetIndexType('index', 'nonExistant');
}
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testResetIndexTypeShouldThrowExceptionForInvalidType()
+ public function testPostPopulateWithoutAlias()
{
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'c');
+ $this->mockIndex('index', new IndexConfig('index', array(), array()));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+ $this->aliasProcessor->expects($this->never())
+ ->method('switchIndexAlias');
+
+ $this->resetter->postPopulate('index');
}
- public function testResetIndexTypeIgnoreTypeMissingException()
+ public function testPostPopulate()
{
- $type = $this->getMockElasticaType();
+ $indexConfig = new IndexConfig('index', array(), array( 'useAlias' => true));
+ $index = $this->mockIndex('index', $indexConfig);
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
+ $this->aliasProcessor->expects($this->once())
+ ->method('switchIndexAlias')
+ ->with($indexConfig, $index);
- $type->expects($this->once())
- ->method('delete')
- ->will($this->throwException(new ResponseException(
- new Request(''),
- new Response(array('error' => 'TypeMissingException[[de_20131022] type[bla] missing]', 'status' => 404)))
- ));
-
- $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
- $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'a');
+ $this->resetter->postPopulate('index');
}
- public function testIndexMappingForParent()
+ private function dispatcherExpects(array $events)
{
- $type = $this->getMockElasticaType();
+ $expectation = $this->dispatcher->expects($this->exactly(count($events)))
+ ->method('dispatch');
- $this->indexConfigsByName['parent']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
-
- $type->expects($this->once())
- ->method('delete');
-
- $mapping = Mapping::create($this->indexConfigsByName['parent']['config']['properties']['a']['properties']);
- $mapping->setParam('_parent', array('type' => 'b'));
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('parent', 'a');
+ call_user_func_array(array($expectation, 'withConsecutive'), $events);
}
- /**
- * @return \Elastica\Index
- */
- private function getMockElasticaIndex()
+ private function mockIndex($indexName, IndexConfig $config, $mapping = array())
{
- return $this->getMockBuilder('Elastica\Index')
+ $this->configManager->expects($this->atLeast(1))
+ ->method('getIndexConfiguration')
+ ->with($indexName)
+ ->will($this->returnValue($config));
+ $index = new Index($this->elasticaClient, $indexName);
+ $this->indexManager->expects($this->any())
+ ->method('getIndex')
+ ->with($indexName)
+ ->willReturn($index);
+ $this->mappingBuilder->expects($this->any())
+ ->method('buildIndexMapping')
+ ->with($config)
+ ->willReturn($mapping);
+
+ return $index;
+ }
+
+ private function mockType($typeName, $indexName, TypeConfig $config, $mapping = array())
+ {
+ $this->configManager->expects($this->atLeast(1))
+ ->method('getTypeConfiguration')
+ ->with($indexName, $typeName)
+ ->will($this->returnValue($config));
+ $index = new Index($this->elasticaClient, $indexName);
+ $this->indexManager->expects($this->once())
+ ->method('getIndex')
+ ->with($indexName)
+ ->willReturn($index);
+ $this->mappingBuilder->expects($this->once())
+ ->method('buildTypeMapping')
+ ->with($config)
+ ->willReturn($mapping);
+
+ return $index;
+ }
+
+ protected function setUp()
+ {
+ $this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
->disableOriginalConstructor()
->getMock();
- }
-
- /**
- * @return \Elastica\Type
- */
- private function getMockElasticaType()
- {
- return $this->getMockBuilder('Elastica\Type')
+ $this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
->disableOriginalConstructor()
->getMock();
+ $this->dispatcher = $this->getMockBuilder('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface')
+ ->getMock();
+ $this->elasticaClient = $this->getMockBuilder('Elastica\\Client')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->resetter = new Resetter(
+ $this->configManager,
+ $this->indexManager,
+ $this->aliasProcessor,
+ $this->mappingBuilder,
+ $this->dispatcher
+ );
}
}
diff --git a/Tests/Provider/IndexableTest.php b/Tests/Provider/IndexableTest.php
index 3080c88..e122ec1 100644
--- a/Tests/Provider/IndexableTest.php
+++ b/Tests/Provider/IndexableTest.php
@@ -55,6 +55,7 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
{
return array(
array('nonexistentEntityMethod'),
+ array(array('@indexableService', 'internalMethod')),
array(array(new IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
@@ -67,10 +68,13 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
array('isIndexable', false),
array(array(new IndexableDecider(), 'isIndexable'), true),
array(array('@indexableService', 'isIndexable'), true),
+ array(array('@indexableService'), true),
array(function (Entity $entity) { return $entity->maybeIndex(); }, true),
array('entity.maybeIndex()', true),
array('!object.isIndexable() && entity.property == "abc"', true),
array('entity.property != "abc"', false),
+ array('["array", "values"]', true),
+ array('[]', false)
);
}
@@ -111,4 +115,9 @@ class IndexableDecider
protected function internalMethod()
{
}
+
+ public function __invoke($object)
+ {
+ return true;
+ }
}