commit
73093beadb
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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('<error>%s</error>', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (BulkResponseException $e) {
|
||||
if (!$options['ignore_errors']) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (null !== $loggerClosure) {
|
||||
$loggerClosure(
|
||||
$options['batch_size'],
|
||||
$nbObjects,
|
||||
sprintf('<error>%s</error>', $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();
|
||||
}
|
||||
|
|
|
@ -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}();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
223
Tests/Index/AliasProcessorTest.php
Normal file
223
Tests/Index/AliasProcessorTest.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the FOSElasticaBundle project.
|
||||
*
|
||||
* (c) Tim Nagel <tim@nagel.com.au>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue