Merge branch '3.1.x'

This commit is contained in:
Tim Nagel 2015-03-18 09:40:06 +11:00
commit a59f2015b4
43 changed files with 1016 additions and 541 deletions

View file

@ -9,7 +9,7 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/commit/XXX where XXX is
the commit hash. To get the diff between two versions, go to the commit hash. To get the diff between two versions, go to
https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0 https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
* 3.1.0 (Unreleased) * 3.1.0 (2015-03-18)
* BC BREAK: `Doctrine\Listener#scheduleForDeletion` access changed to private. * BC BREAK: `Doctrine\Listener#scheduleForDeletion` access changed to private.
* BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that * BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
@ -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 * 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 `POST_TYPE_RESET` are run before and after operations that will reset an
index. #744 index. #744
* Added indexable callback support for the __invoke method of a service. #823

View file

@ -90,8 +90,12 @@ class PopulateCommand extends ContainerAwareCommand
$index = $input->getOption('index'); $index = $input->getOption('index');
$type = $input->getOption('type'); $type = $input->getOption('type');
$reset = !$input->getOption('no-reset'); $reset = !$input->getOption('no-reset');
$options = $input->getOptions(); $options = array(
$options['ignore-errors'] = $input->getOption('ignore-errors'); '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')) { if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */ /** @var DialogHelper $dialog */

View file

@ -502,7 +502,7 @@ class FOSElasticaExtension extends Extension
break; break;
} }
if ($tagName) { if (null !== $tagName) {
foreach ($this->getDoctrineEvents($typeConfig) as $event) { foreach ($this->getDoctrineEvents($typeConfig) as $event) {
$listenerDef->addTag($tagName, array('event' => $event)); $listenerDef->addTag($tagName, array('event' => $event));
} }
@ -527,7 +527,6 @@ class FOSElasticaExtension extends Extension
break; break;
default: default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver'])); throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
break;
} }
$events = array(); $events = array();

View file

@ -2,20 +2,22 @@
namespace FOS\ElasticaBundle\Doctrine; namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\Persistence\ManagerRegistry;
use FOS\ElasticaBundle\HybridResult; use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface; use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer as BaseTransformer;
use FOS\ElasticaBundle\Transformer\HighlightableModelInterface; use FOS\ElasticaBundle\Transformer\HighlightableModelInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/** /**
* Maps Elastica documents with Doctrine objects * Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between * This mapper assumes an exact match between
* elastica documents ids and doctrine object ids. * elastica documents ids and doctrine object ids.
*/ */
abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface abstract class AbstractElasticaToModelTransformer extends BaseTransformer
{ {
/** /**
* Manager registry. * Manager registry.
*
* @var ManagerRegistry
*/ */
protected $registry = null; protected $registry = null;
@ -38,21 +40,14 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
'query_builder_method' => 'createQueryBuilder', 'query_builder_method' => 'createQueryBuilder',
); );
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/** /**
* Instantiates a new Mapper. * Instantiates a new Mapper.
* *
* @param object $registry * @param ManagerRegistry $registry
* @param string $objectClass * @param string $objectClass
* @param array $options * @param array $options
*/ */
public function __construct($registry, $objectClass, array $options = array()) public function __construct(ManagerRegistry $registry, $objectClass, array $options = array())
{ {
$this->registry = $registry; $this->registry = $registry;
$this->objectClass = $objectClass; $this->objectClass = $objectClass;
@ -69,16 +64,6 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
return $this->objectClass; return $this->objectClass;
} }
/**
* Set the PropertyAccessor.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/** /**
* Transforms an array of elastica objects into an array of * Transforms an array of elastica objects into an array of
* model objects fetched from the doctrine repository. * model objects fetched from the doctrine repository.
@ -111,10 +96,7 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
// sort objects in the order of ids // sort objects in the order of ids
$idPos = array_flip($ids); $idPos = array_flip($ids);
$identifier = $this->options['identifier']; $identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor; usort($objects, $this->getSortingClosure($idPos, $identifier));
usort($objects, function ($a, $b) use ($idPos, $identifier, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
});
return $objects; return $objects;
} }
@ -138,7 +120,7 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
} }
/** /**
* {@inheritdoc} * {@inheritDoc}
*/ */
public function getIdentifierField() public function getIdentifierField()
{ {

View file

@ -7,6 +7,7 @@ use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface; use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider; use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
use FOS\ElasticaBundle\Provider\IndexableInterface; use FOS\ElasticaBundle\Provider\IndexableInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class AbstractProvider extends BaseAbstractProvider abstract class AbstractProvider extends BaseAbstractProvider
{ {
@ -26,7 +27,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
* @param ObjectPersisterInterface $objectPersister * @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable * @param IndexableInterface $indexable
* @param string $objectClass * @param string $objectClass
* @param array $options * @param array $baseOptions
* @param ManagerRegistry $managerRegistry * @param ManagerRegistry $managerRegistry
* @param SliceFetcherInterface $sliceFetcher * @param SliceFetcherInterface $sliceFetcher
*/ */
@ -34,71 +35,106 @@ abstract class AbstractProvider extends BaseAbstractProvider
ObjectPersisterInterface $objectPersister, ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable, IndexableInterface $indexable,
$objectClass, $objectClass,
array $options, array $baseOptions,
ManagerRegistry $managerRegistry, ManagerRegistry $managerRegistry,
SliceFetcherInterface $sliceFetcher = null SliceFetcherInterface $sliceFetcher = null
) { ) {
parent::__construct($objectPersister, $indexable, $objectClass, array_merge(array( parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions);
'clear_object_manager' => true,
'debug_logging' => false,
'ignore_errors' => false,
'query_builder_method' => 'createQueryBuilder',
), $options));
$this->managerRegistry = $managerRegistry; $this->managerRegistry = $managerRegistry;
$this->sliceFetcher = $sliceFetcher; $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} * {@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); $manager = $this->managerRegistry->getManagerForClass($this->objectClass);
$objects = array(); $queryBuilder = $this->createQueryBuilder($options['query_builder_method']);
for (; $offset < $nbObjects; $offset += $batchSize) { $nbObjects = $this->countObjects($queryBuilder);
$objects = $this->getSlice($queryBuilder, $batchSize, $offset, $objects); $offset = $options['offset'];
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
if ($objects) { $objects = array();
if (!$ignoreErrors) { 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); $this->objectPersister->insertMany($objects);
} else { }
try { } catch (BulkResponseException $e) {
$this->objectPersister->insertMany($objects); if (!$options['ignore_errors']) {
} catch (BulkResponseException $e) { throw $e;
if ($loggerClosure) { }
$loggerClosure($batchSize, $nbObjects, sprintf('<error>%s</error>', $e->getMessage()));
} 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(); $manager->clear();
} }
usleep($sleep); usleep($options['sleep']);
if ($loggerClosure) { if (null !== $loggerClosure) {
$loggerClosure($batchSize, $nbObjects); $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 * @return array
*/ */
protected function getSlice($queryBuilder, $limit, $offset, $lastSlice) private function getSlice($queryBuilder, $limit, $offset, $lastSlice)
{ {
if (!$this->sliceFetcher) { if (!$this->sliceFetcher) {
return $this->fetchSlice($queryBuilder, $limit, $offset); return $this->fetchSlice($queryBuilder, $limit, $offset);
@ -131,47 +167,4 @@ abstract class AbstractProvider extends BaseAbstractProvider
$identifierFieldNames $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();
} }

View file

@ -3,8 +3,8 @@
namespace FOS\ElasticaBundle\Doctrine; namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs; use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister; use FOS\ElasticaBundle\Persister\ObjectPersister;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\IndexableInterface; use FOS\ElasticaBundle\Provider\IndexableInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccess;
@ -19,14 +19,14 @@ class Listener
/** /**
* Object persister. * Object persister.
* *
* @var ObjectPersister * @var ObjectPersisterInterface
*/ */
protected $objectPersister; protected $objectPersister;
/** /**
* Configuration for the listener. * Configuration for the listener.
* *
* @var string * @var array
*/ */
private $config; private $config;
@ -84,7 +84,7 @@ class Listener
$this->objectPersister = $objectPersister; $this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor(); $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($logger) { if ($logger && $this->objectPersister instanceof ObjectPersister) {
$this->objectPersister->setLogger($logger); $this->objectPersister->setLogger($logger);
} }
} }

View file

@ -44,7 +44,7 @@ class Provider extends AbstractProvider
} }
/** /**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects() * {@inheritDoc}
*/ */
protected function countObjects($queryBuilder) 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) 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 return $this->managerRegistry
->getManagerForClass($this->objectClass) ->getManagerForClass($this->objectClass)
->getRepository($this->objectClass) ->getRepository($this->objectClass)
->{$this->options['query_builder_method']}(); ->{$method}();
} }
} }

View file

@ -46,7 +46,7 @@ class Provider extends AbstractProvider
} }
/** /**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects() * {@inheritDoc}
*/ */
protected function countObjects($queryBuilder) 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) 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 return $this->managerRegistry
->getManagerForClass($this->objectClass) ->getManagerForClass($this->objectClass)
->getRepository($this->objectClass) ->getRepository($this->objectClass)
// ORM query builders require an alias argument // ORM query builders require an alias argument
->{$this->options['query_builder_method']}(static::ENTITY_ALIAS); ->{$method}(static::ENTITY_ALIAS);
} }
} }

View file

@ -14,6 +14,9 @@ use FOS\ElasticaBundle\Doctrine\SliceFetcherInterface;
class SliceFetcher implements SliceFetcherInterface class SliceFetcher implements SliceFetcherInterface
{ {
/** /**
* This method should remain in sync with Provider::fetchSlice until that method is deprecated and
* removed.
*
* {@inheritdoc} * {@inheritdoc}
*/ */
public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames) public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames)

38
Event/IndexEvent.php Normal file
View file

@ -0,0 +1,38 @@
<?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\Event;
use Symfony\Component\EventDispatcher\Event;
class IndexEvent extends Event
{
/**
* @var string
*/
private $index;
/**
* @param string $index
*/
public function __construct($index)
{
$this->index = $index;
}
/**
* @return string
*/
public function getIndex()
{
return $this->index;
}
}

View file

@ -11,23 +11,16 @@
namespace FOS\ElasticaBundle\Event; namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
/** /**
* Index Populate Event. * Index Populate Event.
* *
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv> * @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/ */
class IndexPopulateEvent extends Event class IndexPopulateEvent extends IndexEvent
{ {
const PRE_INDEX_POPULATE = 'elastica.index.index_pre_populate'; const PRE_INDEX_POPULATE = 'elastica.index.index_pre_populate';
const POST_INDEX_POPULATE = 'elastica.index.index_post_populate'; const POST_INDEX_POPULATE = 'elastica.index.index_post_populate';
/**
* @var string
*/
private $index;
/** /**
* @var bool * @var bool
*/ */
@ -45,19 +38,12 @@ class IndexPopulateEvent extends Event
*/ */
public function __construct($index, $reset, $options) public function __construct($index, $reset, $options)
{ {
$this->index = $index; parent::__construct($index);
$this->reset = $reset; $this->reset = $reset;
$this->options = $options; $this->options = $options;
} }
/**
* @return string
*/
public function getIndex()
{
return $this->index;
}
/** /**
* @return boolean * @return boolean
*/ */

View file

@ -11,14 +11,12 @@
namespace FOS\ElasticaBundle\Event; namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
/** /**
* Index ResetEvent. * Index ResetEvent.
* *
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv> * @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/ */
class IndexResetEvent extends Event class IndexResetEvent extends IndexEvent
{ {
const PRE_INDEX_RESET = 'elastica.index.pre_reset'; const PRE_INDEX_RESET = 'elastica.index.pre_reset';
const POST_INDEX_RESET = 'elastica.index.post_reset'; const POST_INDEX_RESET = 'elastica.index.post_reset';
@ -28,11 +26,6 @@ class IndexResetEvent extends Event
*/ */
private $force; private $force;
/**
* @var string
*/
private $index;
/** /**
* @var bool * @var bool
*/ */
@ -45,17 +38,10 @@ class IndexResetEvent extends Event
*/ */
public function __construct($index, $populating, $force) public function __construct($index, $populating, $force)
{ {
$this->force = $force; parent::__construct($index);
$this->index = $index;
$this->populating = $populating;
}
/** $this->force = $force;
* @return string $this->populating = $populating;
*/
public function getIndex()
{
return $this->index;
} }
/** /**

View file

@ -18,16 +18,11 @@ use Symfony\Component\EventDispatcher\Event;
* *
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv> * @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/ */
class TypeResetEvent extends Event class TypeResetEvent extends IndexEvent
{ {
const PRE_TYPE_RESET = 'elastica.index.type_pre_reset'; const PRE_TYPE_RESET = 'elastica.index.type_pre_reset';
const POST_TYPE_RESET = 'elastica.index.type_post_reset'; const POST_TYPE_RESET = 'elastica.index.type_post_reset';
/**
* @var string
*/
private $index;
/** /**
* @var string * @var string
*/ */
@ -39,16 +34,9 @@ class TypeResetEvent extends Event
*/ */
public function __construct($index, $type) public function __construct($index, $type)
{ {
$this->type = $type; parent::__construct($index);
$this->index = $index;
}
/** $this->type = $type;
* @return string
*/
public function getIndex()
{
return $this->index;
} }
/** /**

View file

@ -6,6 +6,6 @@ class AliasIsIndexException extends \Exception
{ {
public function __construct($indexName) 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));
} }
} }

View file

@ -54,37 +54,47 @@ class AliasProcessor
$client = $index->getClient(); $client = $index->getClient();
$aliasName = $indexConfig->getElasticSearchName(); $aliasName = $indexConfig->getElasticSearchName();
$oldIndexName = false; $oldIndexName = null;
$newIndexName = $index->getName(); $newIndexName = $index->getName();
try { try {
$aliasedIndexes = $this->getAliasedIndexes($client, $aliasName); $oldIndexName = $this->getAliasedIndex($client, $aliasName);
} catch (AliasIsIndexException $e) { } catch (AliasIsIndexException $e) {
if (!$force) { if (!$force) {
throw $e; throw $e;
} }
$this->deleteIndex($client, $aliasName); $this->deleteIndex($client, $aliasName);
$aliasedIndexes = array();
} }
if (count($aliasedIndexes) > 1) { try {
throw new \RuntimeException( $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
sprintf( $client->request('_aliases', 'POST', $aliasUpdateRequest);
'Alias %s is used for multiple indexes: [%s]. } catch (ExceptionInterface $e) {
Make sure it\'s either not used or is assigned to one index only', $this->cleanupRenameFailure($client, $newIndexName, $e);
$aliasName,
implode(', ', $aliasedIndexes)
)
);
} }
// 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()); $aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) === 1) { if (null !== $aliasedIndex) {
// if the alias is set - add an action to remove it // if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array( $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), 'add' => array('index' => $newIndexName, 'alias' => $aliasName),
); );
try { return $aliasUpdateRequest;
$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()
);
}
throw new \RuntimeException( /**
sprintf( * Cleans up an index when we encounter a failure to rename the alias.
'Failed to updated index alias: %s. %s', *
$renameAliasException->getMessage(), * @param Client $client
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName) * @param string $indexName
), 0, $renameAliasException * @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 throw new \RuntimeException(sprintf(
if ($oldIndexName) { 'Failed to updated index alias: %s. %s',
$oldIndex = new Index($client, $oldIndexName); $renameAliasException->getMessage(),
try { $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
$oldIndex->delete(); ), 0, $renameAliasException);
} catch (ExceptionInterface $deleteOldIndexException) { }
throw new \RuntimeException(
sprintf( /**
'Failed to delete old index %s with message: %s', * Delete an index.
$oldIndexName, *
$deleteOldIndexException->getMessage() * @param Client $client
), 0, $deleteOldIndexException * @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 Client $client
* @param string $aliasName Alias name * @param string $aliasName Alias name
* *
* @return array * @return string|null
* *
* @throws AliasIsIndexException * @throws AliasIsIndexException
*/ */
private function getAliasedIndexes(Client $client, $aliasName) private function getAliasedIndex(Client $client, $aliasName)
{ {
$aliasesInfo = $client->request('_aliases', 'GET')->getData(); $aliasesInfo = $client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array(); $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)
));
}
/** return array_shift($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);
} }
} }

View file

@ -6,6 +6,11 @@ use FOS\ElasticaBundle\Elastica\Index;
class IndexManager class IndexManager
{ {
/**
* @var Index
*/
private $defaultIndex;
/** /**
* @var array * @var array
*/ */

View file

@ -38,13 +38,13 @@ class MappingBuilder
} }
$mapping = array(); $mapping = array();
if ($typeMappings) { if (!empty($typeMappings)) {
$mapping['mappings'] = $typeMappings; $mapping['mappings'] = $typeMappings;
} }
// 'warmers' => $indexConfig->getWarmers(), // 'warmers' => $indexConfig->getWarmers(),
$settings = $indexConfig->getSettings(); $settings = $indexConfig->getSettings();
if ($settings) { if (!empty($settings)) {
$mapping['settings'] = $settings; $mapping['settings'] = $settings;
} }
@ -95,7 +95,7 @@ class MappingBuilder
$mapping['_meta']['model'] = $typeConfig->getModel(); $mapping['_meta']['model'] = $typeConfig->getModel();
} }
if (!$mapping) { if (empty($mapping)) {
// Empty mapping, we want it encoded as a {} instead of a [] // Empty mapping, we want it encoded as a {} instead of a []
$mapping = new \stdClass(); $mapping = new \stdClass();
} }

View file

@ -86,12 +86,12 @@ class Resetter
*/ */
public function resetIndex($indexName, $populating = false, $force = false) 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); $indexConfig = $this->configManager->getIndexConfiguration($indexName);
$index = $this->indexManager->getIndex($indexName); $index = $this->indexManager->getIndex($indexName);
$event = new IndexResetEvent($indexName, $populating, $force);
$this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
if ($indexConfig->isUseAlias()) { if ($indexConfig->isUseAlias()) {
$this->aliasProcessor->setRootName($indexConfig, $index); $this->aliasProcessor->setRootName($indexConfig, $index);
} }
@ -117,12 +117,12 @@ class Resetter
*/ */
public function resetIndexType($indexName, $typeName) public function resetIndexType($indexName, $typeName)
{ {
$event = new TypeResetEvent($indexName, $typeName);
$this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
$typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName); $typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName);
$type = $this->indexManager->getIndex($indexName)->getType($typeName); $type = $this->indexManager->getIndex($indexName)->getType($typeName);
$event = new TypeResetEvent($indexName, $typeName);
$this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
try { try {
$type->delete(); $type->delete();
} catch (ResponseException $e) { } catch (ResponseException $e) {

View file

@ -74,7 +74,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
? (integer) $this->query->getParam('size') ? (integer) $this->query->getParam('size')
: null; : null;
if ($size && $size < $offset + $itemCountPerPage) { if (null !== $size && $size < $offset + $itemCountPerPage) {
$itemCountPerPage = $size - $offset; $itemCountPerPage = $size - $offset;
} }

View file

@ -10,6 +10,15 @@ namespace FOS\ElasticaBundle\Persister;
*/ */
interface ObjectPersisterInterface interface ObjectPersisterInterface
{ {
/**
* Checks if this persister can handle the given object or not.
*
* @param mixed $object
*
* @return boolean
*/
public function handlesObject($object);
/** /**
* Insert one object into the type * Insert one object into the type
* The object will be transformed to an elastica document. * The object will be transformed to an elastica document.
@ -66,13 +75,4 @@ interface ObjectPersisterInterface
* @param array $identifiers array of domain model object identifiers * @param array $identifiers array of domain model object identifiers
*/ */
public function deleteManyByIdentifiers(array $identifiers); public function deleteManyByIdentifiers(array $identifiers);
/**
* If the object persister handles the given object.
*
* @param object $object
*
* @return bool
*/
public function handlesObject($object);
} }

View file

@ -18,12 +18,16 @@ class ObjectSerializerPersister extends ObjectPersister
protected $serializer; protected $serializer;
/** /**
* @param Type $type
* @param ModelToElasticaTransformerInterface $transformer
* @param string $objectClass * @param string $objectClass
* @param callable $serializer
*/ */
public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer) public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer)
{ {
parent::__construct($type, $transformer, $objectClass, array()); parent::__construct($type, $transformer, $objectClass, array());
$this->serializer = $serializer;
$this->serializer = $serializer;
} }
/** /**

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Propel; namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\HybridResult; use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface; use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@ -14,7 +15,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
* *
* @author William Durand <william.durand1@gmail.com> * @author William Durand <william.durand1@gmail.com>
*/ */
class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{ {
/** /**
* Propel model class to map to Elastica documents. * Propel model class to map to Elastica documents.
@ -33,13 +34,6 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
'identifier' => 'id', 'identifier' => 'id',
); );
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/** /**
* Constructor. * Constructor.
* *
@ -52,16 +46,6 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$this->options = array_merge($this->options, $options); $this->options = array_merge($this->options, $options);
} }
/**
* Set the PropertyAccessor instance.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/** /**
* Transforms an array of Elastica document into an array of Propel entities * Transforms an array of Elastica document into an array of Propel entities
* fetched from the database. * fetched from the database.
@ -82,11 +66,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
// Sort objects in the order of their IDs // Sort objects in the order of their IDs
$idPos = array_flip($ids); $idPos = array_flip($ids);
$identifier = $this->options['identifier']; $identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor; $sortCallback = $this->getSortingClosure($idPos, $identifier);
$sortCallback = function ($a, $b) use ($idPos, $identifier, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
};
if (is_object($objects)) { if (is_object($objects)) {
$objects->uasort($sortCallback); $objects->uasort($sortCallback);
@ -105,7 +85,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$objects = $this->transform($elasticaObjects); $objects = $this->transform($elasticaObjects);
$result = array(); $result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) { for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]); $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
} }

View file

@ -14,31 +14,43 @@ class Provider extends AbstractProvider
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function populate(\Closure $loggerClosure = null, array $options = array()) public function doPopulate($options, \Closure $loggerClosure = null)
{ {
$queryClass = $this->objectClass.'Query'; $queryClass = $this->objectClass.'Query';
$nbObjects = $queryClass::create()->count(); $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() $objects = $queryClass::create()
->limit($batchSize) ->limit($options['batch_size'])
->offset($offset) ->offset($offset)
->find() ->find()
->getArrayCopy(); ->getArrayCopy();
$objects = $this->filterObjects($options, $objects);
$objects = array_filter($objects, array($this, 'isObjectIndexable')); if (!empty($objects)) {
if ($objects) {
$this->objectPersister->insertMany($objects); $this->objectPersister->insertMany($objects);
} }
usleep($sleep); usleep($options['sleep']);
if ($loggerClosure) { if ($loggerClosure) {
$loggerClosure($batchSize, $nbObjects); $loggerClosure($options['batch_size'], $nbObjects);
} }
} }
} }
/**
* {@inheritDoc}
*/
protected function disableLogging()
{
}
/**
* {@inheritDoc}
*/
protected function enableLogging($logger)
{
}
} }

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Provider; namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface; use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/** /**
* AbstractProvider. * AbstractProvider.
@ -10,9 +11,9 @@ use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
abstract class AbstractProvider implements ProviderInterface abstract class AbstractProvider implements ProviderInterface
{ {
/** /**
* @var ObjectPersisterInterface * @var array
*/ */
protected $objectPersister; protected $baseOptions;
/** /**
* @var string * @var string
@ -20,12 +21,17 @@ abstract class AbstractProvider implements ProviderInterface
protected $objectClass; protected $objectClass;
/** /**
* @var array * @var ObjectPersisterInterface
*/ */
protected $options; protected $objectPersister;
/** /**
* @var Indexable * @var OptionsResolver
*/
protected $resolver;
/**
* @var IndexableInterface
*/ */
private $indexable; private $indexable;
@ -35,26 +41,114 @@ abstract class AbstractProvider implements ProviderInterface
* @param ObjectPersisterInterface $objectPersister * @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable * @param IndexableInterface $indexable
* @param string $objectClass * @param string $objectClass
* @param array $options * @param array $baseOptions
*/ */
public function __construct( public function __construct(
ObjectPersisterInterface $objectPersister, ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable, IndexableInterface $indexable,
$objectClass, $objectClass,
array $options = array() array $baseOptions = array()
) { ) {
$this->baseOptions = $baseOptions;
$this->indexable = $indexable; $this->indexable = $indexable;
$this->objectClass = $objectClass; $this->objectClass = $objectClass;
$this->objectPersister = $objectPersister; $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, '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. * Checks if a given object should be indexed or not.
* *
* @deprecated To be removed in 4.0
*
* @param object $object * @param object $object
* *
* @return bool * @return bool
@ -62,8 +156,8 @@ abstract class AbstractProvider implements ProviderInterface
protected function isObjectIndexable($object) protected function isObjectIndexable($object)
{ {
return $this->indexable->isObjectIndexable( return $this->indexable->isObjectIndexable(
$this->options['indexName'], $this->baseOptions['indexName'],
$this->options['typeName'], $this->baseOptions['typeName'],
$object $object
); );
} }
@ -82,4 +176,17 @@ abstract class AbstractProvider implements ProviderInterface
return sprintf('(RAM : current=%uMo peak=%uMo)', $memory, $memoryMax); 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));
}
} }

View file

@ -55,6 +55,7 @@ class Indexable implements IndexableInterface
/** /**
* @param array $callbacks * @param array $callbacks
* @param ContainerInterface $container
*/ */
public function __construct(array $callbacks, ContainerInterface $container) public function __construct(array $callbacks, ContainerInterface $container)
{ {
@ -81,7 +82,7 @@ class Indexable implements IndexableInterface
} }
if ($callback instanceof Expression) { if ($callback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($callback, array( return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object, 'object' => $object,
$this->getExpressionVar($object) => $object, $this->getExpressionVar($object) => $object,
)); ));
@ -112,39 +113,48 @@ class Indexable implements IndexableInterface
return $callback; return $callback;
} }
if (is_array($callback)) { if (is_array($callback) && !is_object($callback[0])) {
list($class, $method) = $callback + array(null, null); return $this->processArrayToCallback($type, $callback);
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_string($callback) && $expression = $this->getExpressionLanguage()) { if (is_string($callback)) {
$callback = new Expression($callback); return $this->buildExpressionCallback($type, $object, $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);
}
} }
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type)); 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. * 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() private function getExpressionLanguage()
{ {
if (null === $this->expressionLanguage) { if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
$this->expressionLanguage = new 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 * @param mixed $object
* *
* @return string * @return string
*/ */
private function getExpressionVar($object = null) private function getExpressionVar($object = null)
{ {
if (!is_object($object)) {
return 'object';
}
$ref = new \ReflectionClass($object); $ref = new \ReflectionClass($object);
return strtolower($ref->getShortName()); 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
));
}
} }

View file

@ -201,13 +201,18 @@ index enabled users.
The callback option supports multiple approaches: The callback option supports multiple approaches:
* A method on the object itself provided as a string. `enabled` will call * 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 * 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 and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
method on a service defined as my_custom_service. 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 * 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' ]` the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
will call Acme\DemoBundle\IndexableChecker::isIndexable($object) 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 * If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
expression provided as a string. The object being indexed will be supplied as `object` expression provided as a string. The object being indexed will be supplied as `object`
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more

View file

@ -8,7 +8,7 @@ use JMS\Serializer\SerializerInterface;
class Callback class Callback
{ {
protected $serializer; protected $serializer;
protected $groups; protected $groups = array();
protected $version; protected $version;
public function setSerializer($serializer) public function setSerializer($serializer)
@ -23,10 +23,8 @@ class Callback
{ {
$this->groups = $groups; $this->groups = $groups;
if ($this->groups) { if (!empty($this->groups) && !$this->serializer instanceof SerializerInterface) {
if (!$this->serializer instanceof SerializerInterface) { throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
}
} }
} }
@ -34,10 +32,8 @@ class Callback
{ {
$this->version = $version; $this->version = $version;
if ($this->version) { if ($this->version && !$this->serializer instanceof SerializerInterface) {
if (!$this->serializer instanceof SerializerInterface) { throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
}
} }
} }
@ -45,7 +41,7 @@ class Callback
{ {
$context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array(); $context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array();
if ($this->groups) { if (!empty($this->groups)) {
$context->setGroups($this->groups); $context->setGroups($this->groups);
} }

View file

@ -9,8 +9,8 @@ use Symfony\Component\DependencyInjection\Container;
class ResetCommandTest extends \PHPUnit_Framework_TestCase class ResetCommandTest extends \PHPUnit_Framework_TestCase
{ {
private $command;
private $resetter; private $resetter;
private $indexManager; private $indexManager;
public function setup() public function setup()

View file

@ -272,6 +272,7 @@ namespace FOS\ElasticaBundle\Tests\Doctrine\Listener;
class Entity class Entity
{ {
private $id; private $id;
public $identifier;
/** /**
* @param integer $id * @param integer $id

View file

@ -58,7 +58,6 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->with('index', 'type', $this->anything()) ->with('index', 'type', $this->anything())
->will($this->returnValue(true)); ->will($this->returnValue(true));
$providerInvocationOffset = 2;
$previousSlice = array(); $previousSlice = array();
foreach ($objectsByIteration as $i => $objects) { foreach ($objectsByIteration as $i => $objects) {
@ -253,7 +252,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('Elastica\Exception\Bulk\ResponseException'); $this->setExpectedException('Elastica\Exception\Bulk\ResponseException');
$provider->populate(null, array('ignore-errors' => false)); $provider->populate(null, array('ignore_errors' => false));
} }
public function testPopulateRunsIndexCallable() public function testPopulateRunsIndexCallable()
@ -281,7 +280,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->objectPersister->expects($this->once()) $this->objectPersister->expects($this->once())
->method('insertMany') ->method('insertMany')
->with(array(1 => 2)); ->with(array(2));
$provider->populate(); $provider->populate();
} }

View file

@ -13,12 +13,12 @@ namespace FOS\ElasticaBundle\Tests\Functional\app\ORM;
class IndexableService class IndexableService
{ {
public function isIndexable($object) public function isIndexable()
{ {
return true; return true;
} }
public static function isntIndexable($object) public static function isntIndexable()
{ {
return false; return false;
} }

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

View file

@ -5,8 +5,14 @@ namespace FOS\ElasticaBundle\Tests\Index;
use Elastica\Exception\ResponseException; use Elastica\Exception\ResponseException;
use Elastica\Request; use Elastica\Request;
use Elastica\Response; use Elastica\Response;
use Elastica\Type;
use Elastica\Type\Mapping; use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\IndexConfig; 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; use FOS\ElasticaBundle\Index\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase class ResetterTest extends \PHPUnit_Framework_TestCase
@ -16,230 +22,253 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
*/ */
private $resetter; private $resetter;
private $configManager;
private $indexManager;
private $aliasProcessor; private $aliasProcessor;
private $configManager;
private $dispatcher;
private $elasticaClient;
private $indexManager;
private $mappingBuilder; 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() public function testResetAllIndexes()
{ {
$indexName = 'index1';
$indexConfig = new IndexConfig($indexName, array(), array());
$this->mockIndex($indexName, $indexConfig);
$this->configManager->expects($this->once()) $this->configManager->expects($this->once())
->method('getIndexNames') ->method('getIndexNames')
->will($this->returnValue(array('index1'))); ->will($this->returnValue(array($indexName)));
$this->configManager->expects($this->once()) $this->dispatcherExpects(array(
->method('getIndexConfiguration') array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
->with('index1') array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
->will($this->returnValue(new IndexConfig('index1', array(), array()))); ));
$this->indexManager->expects($this->once()) $this->elasticaClient->expects($this->exactly(2))
->method('getIndex') ->method('request')
->with('index1') ->withConsecutive(
->will($this->returnValue()); 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(); $this->resetter->resetAllIndexes();
} }
public function testResetIndex() public function testResetIndex()
{ {
$this->indexConfigsByName['foo']['index']->expects($this->once()) $indexConfig = new IndexConfig('index1', array(), array());
->method('create') $this->mockIndex('index1', $indexConfig);
->with($this->indexConfigsByName['foo']['config'], true);
$this->indexConfigsByName['bar']['index']->expects($this->never()) $this->dispatcherExpects(array(
->method('create'); 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); $this->elasticaClient->expects($this->exactly(2))
$resetter->resetIndex('foo'); ->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 * @expectedException \InvalidArgumentException
*/ */
public function testResetIndexShouldThrowExceptionForInvalidIndex() public function testFailureWhenMissingIndexDoesntDispatch()
{ {
$resetter = new Resetter($this->indexConfigsByName); $this->configManager->expects($this->once())
$resetter->resetIndex('baz'); ->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()) $this->dispatcherExpects(array(
->method('getType') array(TypeResetEvent::PRE_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent')),
->with('a') array(TypeResetEvent::POST_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent'))
->will($this->returnValue($type)); ));
$type->expects($this->once()) $this->elasticaClient->expects($this->exactly(2))
->method('delete'); ->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']); $this->resetter->resetIndexType('index', 'type');
$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');
} }
/** /**
* @expectedException \InvalidArgumentException * @expectedException \InvalidArgumentException
*/ */
public function testResetIndexTypeShouldThrowExceptionForInvalidIndex() public function testNonExistantResetType()
{ {
$resetter = new Resetter($this->indexConfigsByName); $this->configManager->expects($this->once())
$resetter->resetIndexType('baz', 'a'); ->method('getTypeConfiguration')
->with('index', 'nonExistant')
->will($this->throwException(new \InvalidArgumentException));
$this->indexManager->expects($this->never())
->method('getIndex');
$this->resetter->resetIndexType('index', 'nonExistant');
} }
/** public function testPostPopulateWithoutAlias()
* @expectedException \InvalidArgumentException
*/
public function testResetIndexTypeShouldThrowExceptionForInvalidType()
{ {
$resetter = new Resetter($this->indexConfigsByName); $this->mockIndex('index', new IndexConfig('index', array(), array()));
$resetter->resetIndexType('foo', 'c');
$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()) $this->aliasProcessor->expects($this->once())
->method('getType') ->method('switchIndexAlias')
->with('a') ->with($indexConfig, $index);
->will($this->returnValue($type));
$type->expects($this->once()) $this->resetter->postPopulate('index');
->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');
} }
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()) call_user_func_array(array($expectation, 'withConsecutive'), $events);
->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');
} }
/** private function mockIndex($indexName, IndexConfig $config, $mapping = array())
* @return \Elastica\Index
*/
private function getMockElasticaIndex()
{ {
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() ->disableOriginalConstructor()
->getMock(); ->getMock();
} $this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
/**
* @return \Elastica\Type
*/
private function getMockElasticaType()
{
return $this->getMockBuilder('Elastica\Type')
->disableOriginalConstructor() ->disableOriginalConstructor()
->getMock(); ->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
);
} }
} }

View file

@ -203,7 +203,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer() private function getTransformer()
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = new ModelToElasticaAutoTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer; return $transformer;
} }

View file

@ -112,7 +112,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer() private function getTransformer()
{ {
$transformer = new ModelToElasticaIdentifierTransformer(); $transformer = new ModelToElasticaIdentifierTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer; return $transformer;
} }

View file

@ -55,6 +55,7 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
{ {
return array( return array(
array('nonexistentEntityMethod'), array('nonexistentEntityMethod'),
array(array('@indexableService', 'internalMethod')),
array(array(new IndexableDecider(), 'internalMethod')), array(array(new IndexableDecider(), 'internalMethod')),
array(42), array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'), array('entity.getIsIndexable() && nonexistentEntityFunction()'),
@ -67,10 +68,13 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
array('isIndexable', false), array('isIndexable', false),
array(array(new IndexableDecider(), 'isIndexable'), true), array(array(new IndexableDecider(), 'isIndexable'), true),
array(array('@indexableService', 'isIndexable'), true), array(array('@indexableService', 'isIndexable'), true),
array(array('@indexableService'), true),
array(function (Entity $entity) { return $entity->maybeIndex(); }, true), array(function (Entity $entity) { return $entity->maybeIndex(); }, true),
array('entity.maybeIndex()', true), array('entity.maybeIndex()', true),
array('!object.isIndexable() && entity.property == "abc"', true), array('!object.isIndexable() && entity.property == "abc"', true),
array('entity.property != "abc"', false), array('entity.property != "abc"', false),
array('["array", "values"]', true),
array('[]', false)
); );
} }
@ -111,4 +115,9 @@ class IndexableDecider
protected function internalMethod() protected function internalMethod()
{ {
} }
public function __invoke($object)
{
return true;
}
} }

View file

@ -57,10 +57,10 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
/** /**
* @param string $testQuery * @param string $testQuery
* @param int $testLimit * @param mixed $testLimit
* @param string $method * @param string $method
* *
* @return \FOS\ElasticaBundle\Finder\TransformedFinder|\PHPUnit_Framework_MockObject_MockObject * @return \FOS\ElasticaBundle\Finder\TransformedFinder
*/ */
private function getFinderMock($testQuery, $testLimit = null, $method = 'find') private function getFinderMock($testQuery, $testLimit = null, $method = 'find')
{ {

View file

@ -37,7 +37,7 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$this->collection = new ElasticaToModelTransformerCollection($this->transformers = array( $this->collection = new ElasticaToModelTransformerCollection($this->transformers = array(
'type1' => $transformer1, 'type1' => $transformer1,
'type2' => $transformer2, 'type2' => $transformer2,
), array()); ));
} }
public function testGetObjectClass() public function testGetObjectClass()

View file

@ -417,7 +417,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
private function getTransformer($dispatcher = null) private function getTransformer($dispatcher = null)
{ {
$transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher); $transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher);
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer; return $transformer;
} }

View file

@ -51,7 +51,7 @@ class ModelToElasticaIdentifierTransformerTest extends \PHPUnit_Framework_TestCa
private function getTransformer() private function getTransformer()
{ {
$transformer = new ModelToElasticaIdentifierTransformer(); $transformer = new ModelToElasticaIdentifierTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer; return $transformer;
} }

View file

@ -0,0 +1,51 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Transformer;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
{
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Set the PropertyAccessor instance.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Returns a sorting closure to be used with usort() to put retrieved objects
* back in the order that they were returned by ElasticSearch.
*
* @param array $idPos
* @param string $identifierPath
* @return callable
*/
protected function getSortingClosure(array $idPos, $identifierPath)
{
$propertyAccessor = $this->propertyAccessor;
return function ($a, $b) use ($idPos, $identifierPath, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifierPath)] > $idPos[$propertyAccessor->getValue($b, $identifierPath)];
};
}
}

View file

@ -81,7 +81,7 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
$objects = $this->transform($elasticaObjects); $objects = $this->transform($elasticaObjects);
$result = array(); $result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) { for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]); $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
} }

View file

@ -3,10 +3,17 @@
namespace FOS\ElasticaBundle\Transformer; namespace FOS\ElasticaBundle\Transformer;
/** /**
* Maps Elastica documents with model objects. * Indicates that the model should have elastica highlights injected.
*/ */
interface HighlightableModelInterface interface HighlightableModelInterface
{ {
/**
* Returns a unique identifier for the model.
*
* @return mixed
*/
public function getId();
/** /**
* Set ElasticSearch highlight data. * Set ElasticSearch highlight data.
* *