Merge branch 'master' into documentation

Conflicts:
	DependencyInjection/Configuration.php
	README.md
This commit is contained in:
Tim Nagel 2014-03-25 16:04:32 +11:00
commit befb0907cc
45 changed files with 1248 additions and 440 deletions

View file

@ -6,4 +6,4 @@ php:
before_script:
- echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- composer install --dev
- composer install --dev --prefer-source

27
CHANGELOG-3.0.md Normal file
View file

@ -0,0 +1,27 @@
CHANGELOG for 3.0.x
===================
This changelog references the relevant changes (bug and security fixes) done
in 3.0 minor versions.
To get the diff for a specific change, go to
https://github.com/FriendsOfSymfony/FOSElasticaBundle/commit/XXX where XXX is
the commit hash. To get the diff between two versions, go to
https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.0...v3.0.1
To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x`
* 3.0.0-ALPHA3 (xxxx-xx-xx)
* #463: allowing hot swappable reindexing
* #415: BC BREAK: document indexing occurs in postFlush rather than the pre* events previously.
* 7d13823: Dropped (broken) support for Symfony <2.3
* #496: Added support for HTTP headers
* 3.0.0-ALPHA2 (2014-03-17)
* 41bf07e: Renamed the `no-stop-on-error` option in PopulateCommand to `ignore-errors`
* 418b9d7: Fixed validation of url configuration
* 726892c: Ignore TypeMissingException when resetting a single type. This allows to create new types without having to recreate the whole index.
* 7f53bad Add support for include_in_{parent,root} for nested and objects

View file

@ -11,6 +11,9 @@ use FOS\ElasticaBundle\Logger\ElasticaLogger;
*/
class Client extends ElasticaClient
{
/**
* {@inheritdoc}
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
$start = microtime(true);
@ -25,11 +28,17 @@ class Client extends ElasticaClient
'host' => $connection->getHost(),
'port' => $connection->getPort(),
'transport' => $connection->getTransport(),
'headers' => $connection->getConfig('headers'),
);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
}
return $response;
}
public function getIndex($name)
{
return new DynamicIndex($this, $name);
}
}

11
Command/PopulateCommand.php Executable file → Normal file
View file

@ -45,6 +45,7 @@ class PopulateCommand extends ContainerAwareCommand
->addOption('offset', null, InputOption::VALUE_REQUIRED, 'Start indexing at offset', 0)
->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Sleep time between persisting iterations (microseconds)', 0)
->addOption('batch-size', null, InputOption::VALUE_REQUIRED, 'Index packet size (overrides provider config option)')
->addOption('ignore-errors', null, InputOption::VALUE_NONE, 'Do not stop on errors')
->setDescription('Populates search indexes from providers')
;
}
@ -66,11 +67,12 @@ class PopulateCommand extends ContainerAwareCommand
{
$index = $input->getOption('index');
$type = $input->getOption('type');
$reset = $input->getOption('no-reset') ? false : true;
$noInteraction = $input->getOption('no-interaction');
$reset = !$input->getOption('no-reset');
$options = $input->getOptions();
if (!$noInteraction && $reset && $input->getOption('offset')) {
$options['ignore-errors'] = $input->hasOption('ignore-errors');
if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
if (!$dialog->askConfirmation($output, '<question>You chose to reset the index and start indexing with an offset. Do you really want to do that?</question>', true)) {
@ -107,7 +109,7 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndex(OutputInterface $output, $index, $reset, $options)
{
if ($reset) {
if ($reset && $this->indexManager->getIndex($index)->exists()) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
$this->resetter->resetIndex($index);
}
@ -124,6 +126,7 @@ class PopulateCommand extends ContainerAwareCommand
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->resetter->postPopulate($index);
$this->indexManager->getIndex($index)->refresh();
}

View file

@ -60,7 +60,7 @@ class ResetCommand extends ContainerAwareCommand
if (null !== $type) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
$this->resetter->resetIndex($index, $type);
$this->resetter->resetIndexType($index, $type);
} else {
$indexes = null === $index
? array_keys($this->indexManager->getAllIndexes())

View file

@ -87,7 +87,8 @@ class Configuration implements ConfigurationInterface
array(
'host' => $v['host'],
'port' => $v['port'],
'logger' => isset($v['logger']) ? $v['logger'] : null
'logger' => isset($v['logger']) ? $v['logger'] : null,
'headers' => isset($v['headers']) ? $v['headers'] : null,
)
)
);
@ -109,16 +110,26 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('servers')
->prototype('array')
->fixXmlConfig('header')
->children()
->scalarNode('url')->end()
->scalarNode('url')
->validate()
->ifTrue(function($url) { return substr($url, -1) !== '/'; })
->then(function($url) { return $url.'/'; })
->end()
->end()
->scalarNode('host')->end()
->scalarNode('port')->end()
->scalarNode('logger')
->info('Set your own logger service for this client or disable the logger with false.')
->defaultValue('fos_elastica.logger')
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
->end()
->arrayNode('headers')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->scalarNode('timeout')->end()
->end()
->end()
->end()
@ -142,18 +153,77 @@ class Configuration implements ConfigurationInterface
->arrayNode('indexes')
->useAttributeAsKey('name')
->prototype('array')
->performNoDeepMerging()
->children()
->scalarNode('index_name')
->info('Defaults to the name of the index, but can be modified if the index name is different in ElasticSearch')
->end()
->booleanNode('use_alias')->defaultValue(false)->end()
->scalarNode('client')->end()
->scalarNode('finder')
->info('Defines an index wide finder that will search all types.')
->treatNullLike(true)
->defaultFalse()
->end()
->append($this->getTypePrototypeNode())
->arrayNode('type_prototype')
->children()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->scalarNode('persist')->defaultValue('postFlush')->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->variableNode('settings')->defaultValue(array())->end()
->end()
->append($this->getTypesNode())
@ -163,20 +233,6 @@ class Configuration implements ConfigurationInterface
;
}
/**
* Builds a type prototype node for the index configuration.
*/
protected function getTypePrototypeNode()
{
$builder = new TreeBuilder();
$node = $builder->root('type_prototype');
$node->info('Allows a prototype type definition that can be applied to all types.');
$this->applyTypeConfiguration($node);
return $node;
}
/**
* Returns the array node used for "types".
*/
@ -185,102 +241,97 @@ class Configuration implements ConfigurationInterface
$builder = new TreeBuilder();
$node = $builder->root('types');
$childrenNode = $node
$node
->useAttributeAsKey('name')
->prototype('array')
->treatNullLike(array());
$this->applyTypeConfiguration($childrenNode);
->treatNullLike(array())
->children()
->arrayNode('serializer')
->addDefaultsIfNotSet()
->children()
->arrayNode('groups')
->treatNullLike(array())
->prototype('scalar')->end()
->end()
->scalarNode('version')->end()
->end()
->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('model')->end()
->scalarNode('repository')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->booleanNode('immediate')->defaultFalse()->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->end()
->append($this->getIdNode())
->append($this->getMappingsNode())
->append($this->getDynamicTemplateNode())
->append($this->getSourceNode())
->append($this->getBoostNode())
->append($this->getRoutingNode())
->append($this->getParentNode())
->append($this->getAllNode())
->append($this->getTimestampNode())
->append($this->getTtlNode())
->end()
;
return $node;
}
/**
* Applies all type configuration fields to a node.
*/
protected function applyTypeConfiguration($node)
{
$node
->children()
->arrayNode('serializer')
->addDefaultsIfNotSet()
->children()
->arrayNode('groups')
->treatNullLike(array())
->prototype('scalar')->end()
->end()
->scalarNode('version')->end()
->end()
->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('model')->end()
->scalarNode('repository')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->end()
->append($this->getIdNode())
->append($this->getMappingsNode())
->append($this->getDynamicTemplateNode())
->append($this->getSourceNode())
->append($this->getBoostNode())
->append($this->getRoutingNode())
->append($this->getParentNode())
->append($this->getAllNode());
}
/**
* Returns the array node used for "mappings".
*/
@ -294,6 +345,10 @@ class Configuration implements ConfigurationInterface
$childrenNode = $node
->useAttributeAsKey('name')
->prototype('array')
->validate()
->ifTrue(function($v) { return isset($v['fields']) && empty($v['fields']); })
->then(function($v) { unset($v['fields']); return $v; })
->end()
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
@ -375,7 +430,8 @@ class Configuration implements ConfigurationInterface
->scalarNode('identifier')->defaultValue('id')->end()
->end()
->end()
->scalarNode('format')->end();
->scalarNode('format')->end()
->scalarNode('similarity')->end();
;
if (isset($nestings['fields'])) {
@ -383,6 +439,10 @@ class Configuration implements ConfigurationInterface
}
if (isset($nestings['properties'])) {
$node
->booleanNode('include_in_parent')->end()
->booleanNode('include_in_root')->end()
;
$this->addNestedFieldConfig($node, $nestings, 'properties');
}
}
@ -623,4 +683,45 @@ class Configuration implements ConfigurationInterface
return $node;
}
/**
* Returns the array node used for "_timestamp"
*/
protected function getTimestampNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_timestamp');
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('path')->end()
->scalarNode('format')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->end()
;
return $node;
}
/**
* Returns the array node used for "_ttl"
*/
protected function getTtlNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_ttl');
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('default')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->end()
;
return $node;
}
}

View file

@ -4,6 +4,7 @@ namespace FOS\ElasticaBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
@ -126,6 +127,7 @@ class FOSElasticaExtension extends Extension
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array(
'index' => new Reference($indexId),
'name_or_alias' => $indexName,
'config' => array(
'mappings' => array()
)
@ -136,6 +138,10 @@ class FOSElasticaExtension extends Extension
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
if ($index['use_alias']) {
$this->indexConfigs[$name]['use_alias'] = true;
}
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig);
}
@ -200,6 +206,10 @@ class FOSElasticaExtension extends Extension
if (isset($type['serializer']['version'])) {
$callbackDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$callbackClassImplementedInterfaces = class_implements($this->serializerConfig['callback_class']); // PHP < 5.4 friendly
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$callbackDef->addMethodCall('setContainer', array(new Reference('service_container')));
}
$container->setDefinition($callbackId, $callbackDef);
@ -248,6 +258,12 @@ class FOSElasticaExtension extends Extension
if (isset($type['_all'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_all'] = $type['_all'];
}
if (isset($type['_timestamp'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_timestamp'] = $type['_timestamp'];
}
if (isset($type['_ttl'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_ttl'] = $type['_ttl'];
}
if (!empty($type['dynamic_templates'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'] = array();
foreach ($type['dynamic_templates'] as $templateName => $templateData) {
@ -439,13 +455,32 @@ class FOSElasticaExtension extends Extension
return $listenerId;
}
/**
* Map Elastica to Doctrine events for the current driver
*/
private function getDoctrineEvents(array $typeConfig)
{
// Flush always calls depending on actions scheduled in lifecycle listeners
$typeConfig['listener']['flush'] = true;
switch ($typeConfig['driver']) {
case 'orm':
$eventsClass = '\Doctrine\ORM\Events';
break;
case 'mongodb':
$eventsClass = '\Doctrine\ODM\MongoDB\Events';
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
break;
}
$events = array();
$eventMapping = array(
'insert' => array('postPersist'),
'update' => array('postUpdate'),
'delete' => array('postRemove', 'preRemove')
'insert' => array(constant($eventsClass.'::postPersist')),
'update' => array(constant($eventsClass.'::postUpdate')),
'delete' => array(constant($eventsClass.'::preRemove')),
'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush'))
);
foreach ($eventMapping as $event => $doctrineEvents) {

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\Persistence\ManagerRegistry;
use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
@ -22,6 +23,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
{
parent::__construct($objectPersister, $objectClass, array_merge(array(
'clear_object_manager' => true,
'ignore_errors' => false,
'query_builder_method' => 'createQueryBuilder',
), $options));
@ -38,6 +40,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
$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'];
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
@ -45,7 +48,17 @@ abstract class AbstractProvider extends BaseAbstractProvider
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
$this->objectPersister->insertMany($objects);
if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
} else {
try {
$this->objectPersister->insertMany($objects);
} catch(BulkResponseException $e) {
if ($loggerClosure) {
$loggerClosure(sprintf('<error>%s</error>',$e->getMessage()));
}
}
}
if ($this->options['clear_object_manager']) {
$this->managerRegistry->getManagerForClass($this->objectClass)->clear();
@ -58,7 +71,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond));
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
}
}
}

View file

@ -2,15 +2,15 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\ObjectManager;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
abstract class AbstractListener implements EventSubscriber
class Listener implements EventSubscriber
{
/**
* Object persister
@ -48,11 +48,11 @@ abstract class AbstractListener implements EventSubscriber
protected $isIndexableCallback;
/**
* Objects scheduled for removal
*
* @var array
* Objects scheduled for insertion, replacement, or removal
*/
private $scheduledForRemoval = array();
public $scheduledForInsertion = array();
public $scheduledForUpdate = array();
public $scheduledForDeletion = array();
/**
* An instance of ExpressionLanguage
@ -149,37 +149,6 @@ abstract class AbstractListener implements EventSubscriber
: call_user_func($this->isIndexableCallback, $object);
}
/**
* Schedules the object for removal.
*
* This is usually called during the pre-remove event.
*
* @param object $object
* @param ObjectManager $objectManager
*/
protected function scheduleForRemoval($object, ObjectManager $objectManager)
{
$metadata = $objectManager->getClassMetadata($this->objectClass);
$esId = $metadata->getFieldValue($object, $this->esIdentifierField);
$this->scheduledForRemoval[spl_object_hash($object)] = $esId;
}
/**
* Removes the object if it was scheduled for removal.
*
* This is usually called during the post-remove event.
*
* @param object $object
*/
protected function removeIfScheduled($object)
{
$objectHash = spl_object_hash($object);
if (isset($this->scheduledForRemoval[$objectHash])) {
$this->objectPersister->deleteById($this->scheduledForRemoval[$objectHash]);
unset($this->scheduledForRemoval[$objectHash]);
}
}
/**
* @param mixed $object
* @return string
@ -207,4 +176,70 @@ abstract class AbstractListener implements EventSubscriber
return $this->expressionLanguage;
}
public function postPersist(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
$this->scheduledForInsertion[] = $entity;
}
}
public function postUpdate(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate[] = $entity;
} else {
// Delete if no longer indexable
$this->scheduledForDeletion[] = $entity;
}
}
}
public function preRemove(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->scheduledForDeletion[] = $entity;
}
}
/**
* Persist scheduled objects to ElasticSearch
*/
private function persistScheduled()
{
if (count($this->scheduledForInsertion)) {
$this->objectPersister->insertMany($this->scheduledForInsertion);
}
if (count($this->scheduledForUpdate)) {
$this->objectPersister->replaceMany($this->scheduledForUpdate);
}
if (count($this->scheduledForDeletion)) {
$this->objectPersister->deleteMany($this->scheduledForDeletion);
}
}
/**
* Iterate through scheduled actions before flushing to emulate 2.x behavior. Note that the ElasticSearch index
* will fall out of sync with the source data in the event of a crash during flush.
*/
public function preFlush(EventArgs $eventArgs)
{
$this->persistScheduled();
}
/**
* Iterating through scheduled actions *after* flushing ensures that the ElasticSearch index will be affected
* only if the query is successful
*/
public function postFlush(EventArgs $eventArgs)
{
$this->persistScheduled();
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\MongoDB;
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass && $this->isObjectIndexable($document)) {
$this->objectPersister->insertOne($document);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
if ($this->isObjectIndexable($document)) {
$this->objectPersister->replaceOne($document);
} else {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
$this->removeIfScheduled($document);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->removeIfScheduled($document);
}
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\ORM;
use Doctrine\ORM\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
$this->objectPersister->insertOne($entity);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
if ($this->isObjectIndexable($entity)) {
$this->objectPersister->replaceOne($entity);
} else {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
$this->removeIfScheduled($entity);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->removeIfScheduled($entity);
}
}
}

28
DynamicIndex.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace FOS\ElasticaBundle;
use Elastica\Index;
/**
* Elastica index capable of reassigning name dynamically
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
*/
class DynamicIndex extends Index
{
/**
* Reassign index name
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
*
* @return void
*/
public function overrideName($name)
{
$this->_name = $name;
}
}

View file

@ -8,10 +8,14 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Bundle.
*
*/
class FOSElasticaBundle extends Bundle
{
/**
* @see Symfony\Component\HttpKernel\Bundle\Bundle::build()
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{

View file

@ -9,7 +9,8 @@ interface FinderInterface
*
* @param mixed $query Can be a string, an array or an \Elastica\Query object
* @param int $limit How many results to get
* @param array $options
* @return array results
*/
function find($query, $limit = null);
function find($query, $limit = null, $options = array());
}

View file

@ -12,15 +12,17 @@ interface PaginatedFinderInterface extends FinderInterface
* Searches for query results and returns them wrapped in a paginator
*
* @param mixed $query Can be a string, an array or an \Elastica\Query object
* @param array $options
* @return Pagerfanta paginated results
*/
function findPaginated($query);
function findPaginated($query, $options = array());
/**
* Creates a paginator adapter for this query
*
* @param mixed $query
* @param array $options
* @return PaginatorAdapterInterface
*/
function createPaginatorAdapter($query);
function createPaginatorAdapter($query, $options = array());
}

View file

@ -29,18 +29,19 @@ class TransformedFinder implements PaginatedFinderInterface
*
* @param string $query
* @param integer $limit
* @param array $options
* @return array of model objects
**/
public function find($query, $limit = null)
public function find($query, $limit = null, $options = array())
{
$results = $this->search($query, $limit);
$results = $this->search($query, $limit, $options);
return $this->transformer->transform($results);
}
public function findHybrid($query, $limit = null)
public function findHybrid($query, $limit = null, $options = array())
{
$results = $this->search($query, $limit);
$results = $this->search($query, $limit, $options);
return $this->transformer->hybridTransform($results);
}
@ -64,15 +65,16 @@ class TransformedFinder implements PaginatedFinderInterface
/**
* @param $query
* @param null|int $limit
* @param array $options
* @return array
*/
protected function search($query, $limit = null)
protected function search($query, $limit = null, $options = array())
{
$queryObject = Query::create($query);
if (null !== $limit) {
$queryObject->setSize($limit);
}
$results = $this->searchable->search($queryObject)->getResults();
$results = $this->searchable->search($queryObject, $options)->getResults();
return $results;
}
@ -81,12 +83,13 @@ class TransformedFinder implements PaginatedFinderInterface
* Gets a paginator wrapping the result of a search
*
* @param string $query
* @param array $options
* @return Pagerfanta
*/
public function findPaginated($query)
public function findPaginated($query, $options = array())
{
$queryObject = Query::create($query);
$paginatorAdapter = $this->createPaginatorAdapter($queryObject);
$paginatorAdapter = $this->createPaginatorAdapter($queryObject, $options);
return new Pagerfanta(new FantaPaginatorAdapter($paginatorAdapter));
}
@ -94,10 +97,10 @@ class TransformedFinder implements PaginatedFinderInterface
/**
* {@inheritdoc}
*/
public function createPaginatorAdapter($query)
public function createPaginatorAdapter($query, $options = array())
{
$query = Query::create($query);
return new TransformedPaginatorAdapter($this->searchable, $query, $this->transformer);
return new TransformedPaginatorAdapter($this->searchable, $query, $options, $this->transformer);
}
}

View file

@ -14,33 +14,44 @@ use Psr\Log\LoggerInterface;
*/
class ElasticaLogger implements LoggerInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
protected $queries;
/**
* @var array
*/
protected $queries = array();
/**
* @var boolean
*/
protected $debug;
/**
* Constructor.
*
* @param LoggerInterface|null $logger The Symfony logger
* @param bool $debug
* @param boolean $debug
*/
public function __construct(LoggerInterface $logger = null, $debug = false)
{
$this->logger = $logger;
$this->queries = array();
$this->debug = $debug;
}
/**
* Logs a query.
*
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data arguments
* @param float $time execution time
* @param array $connection host, port and transport of the query
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data Arguments
* @param float $time Execution time
* @param array $connection Host, port, transport, and headers of the query
* @param array $query Arguments
*/
public function logQuery($path, $method, $data, $time, $connection = array())
public function logQuery($path, $method, $data, $time, $connection = array(), $query = array())
{
if ($this->debug) {
$this->queries[] = array(
@ -48,7 +59,8 @@ class ElasticaLogger implements LoggerInterface
'method' => $method,
'data' => $data,
'executionMS' => $time,
'connection' => $connection
'connection' => $connection,
'queryString' => $query,
);
}
@ -147,6 +159,6 @@ class ElasticaLogger implements LoggerInterface
*/
public function log($level, $message, array $context = array())
{
return $this->logger->log($message, $context);
return $this->logger->log($level, $message, $context);
}
}

View file

@ -22,13 +22,18 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*/
private $query;
/**
* @var array search options
*/
private $options;
/**
* @var integer the number of hits
*/
private $totalHits;
/**
* @array for the facets
* @var array for the facets
*/
private $facets;
@ -36,12 +41,14 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
* @see PaginatorAdapterInterface::__construct
*
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param Query $query the query to search
* @param array $options
*/
public function __construct(SearchableInterface $searchable, Query $query)
public function __construct(SearchableInterface $searchable, Query $query, array $options = array())
{
$this->searchable = $searchable;
$this->query = $query;
$this->options = $options;
}
/**
@ -72,7 +79,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
$query->setFrom($offset);
$query->setSize($itemCountPerPage);
$resultSet = $this->searchable->search($query);
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
return $resultSet;

View file

@ -14,13 +14,14 @@ class TransformedPaginatorAdapter extends RawPaginatorAdapter
private $transformer;
/**
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
* @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
*/
public function __construct(SearchableInterface $searchable, Query $query, ElasticaToModelTransformerInterface $transformer)
public function __construct(SearchableInterface $searchable, Query $query, array $options = array(), ElasticaToModelTransformerInterface $transformer)
{
parent::__construct($searchable, $query);
parent::__construct($searchable, $query, $options);
$this->transformer = $transformer;
}

View file

@ -83,12 +83,12 @@ class ObjectPersister implements ObjectPersisterInterface
} catch (NotFoundException $e) {}
}
/**
* Inserts an array of objects in the type
* Bulk insert an array of objects in the type for the given method
*
* @param array $objects array of domain model objects
**/
* @param string Method to call
*/
public function insertMany(array $objects)
{
$documents = array();
@ -98,6 +98,34 @@ class ObjectPersister implements ObjectPersisterInterface
$this->type->addDocuments($documents);
}
/**
* Bulk updates an array of objects in the type
*
* @param array $objects array of domain model objects
*/
public function replaceMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
$this->type->updateDocuments($documents);
}
/**
* Bulk deletes an array of objects in the type
*
* @param array $objects array of domain model objects
*/
public function deleteMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
$this->type->deleteDocuments($documents);
}
/**
* Transforms an object to an elastica document
*
@ -108,4 +136,4 @@ class ObjectPersister implements ObjectPersisterInterface
{
return $this->transformer->transform($object, $this->fields);
}
}
}

View file

@ -38,13 +38,27 @@ interface ObjectPersisterInterface
* @param mixed $id
*
* @return null
**/
*/
function deleteById($id);
/**
* Inserts an array of objects in the type
* Bulk inserts an array of objects in the type
*
* @param array $objects array of domain model objects
**/
*/
function insertMany(array $objects);
/**
* Bulk updates an array of objects in the type
*
* @param array $objects array of domain model objects
*/
function replaceMany(array $objects);
/**
* Bulk deletes an array of objects in the type
*
* @param array $objects array of domain model objects
*/
function deleteMany(array $objects);
}

View file

@ -23,69 +23,6 @@ class ObjectSerializerPersister extends ObjectPersister
$this->serializer = $serializer;
}
/**
* Insert one object into the type
* The object will be transformed to an elastica document
*
* @param object $object
*/
public function insertOne($object)
{
$document = $this->transformToElasticaDocument($object);
$this->type->addDocument($document);
}
/**
* Replaces one object in the type
*
* @param object $object
* @return null
*/
public function replaceOne($object)
{
$document = $this->transformToElasticaDocument($object);
$this->type->deleteById($document->getId());
$this->type->addDocument($document);
}
/**
* Deletes one object in the type
*
* @param object $object
* @return null
**/
public function deleteOne($object)
{
$document = $this->transformToElasticaDocument($object);
$this->type->deleteById($document->getId());
}
/**
* Deletes one object in the type by id
*
* @param mixed $id
*
* @return null
**/
public function deleteById($id)
{
$this->type->deleteById($id);
}
/**
* Inserts an array of objects in the type
*
* @param array $objects array of domain model objects
**/
public function insertMany(array $objects)
{
$docs = array();
foreach ($objects as $object) {
$docs[] = $this->transformToElasticaDocument($object);
}
$this->type->addDocuments($docs);
}
/**
* Transforms an object to an elastica document
* with just the identifier set

View file

@ -41,7 +41,7 @@ class Provider extends AbstractProvider
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond));
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
}
}
}

View file

@ -4,10 +4,24 @@ namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
/**
* AbstractProvider
*/
abstract class AbstractProvider implements ProviderInterface
{
protected $objectClass;
/**
* @var ObjectPersisterInterface
*/
protected $objectPersister;
/**
* @var string
*/
protected $objectClass;
/**
* @var array
*/
protected $options;
/**
@ -26,4 +40,17 @@ abstract class AbstractProvider implements ProviderInterface
'batch_size' => 100,
), $options);
}
/**
* Get string with RAM usage information (current and peak)
*
* @return string
*/
protected function getMemoryUsage()
{
$memory = round(memory_get_usage() / (1024 * 1024)); // to get usage in Mo
$memoryMax = round(memory_get_peak_usage() / (1024 * 1024)); // to get max usage in Mo
return sprintf('(RAM : current=%uMo peak=%uMo)', $memory, $memoryMax);
}
}

View file

@ -19,23 +19,23 @@ class Repository
$this->finder = $finder;
}
public function find($query, $limit=null)
public function find($query, $limit = null, $options = array())
{
return $this->finder->find($query, $limit);
return $this->finder->find($query, $limit, $options);
}
public function findHybrid($query, $limit=null)
public function findHybrid($query, $limit = null, $options = array())
{
return $this->finder->findHybrid($query, $limit);
return $this->finder->findHybrid($query, $limit, $options);
}
public function findPaginated($query)
public function findPaginated($query, $options = array())
{
return $this->finder->findPaginated($query);
return $this->finder->findPaginated($query, $options);
}
public function createPaginatorAdapter($query)
public function createPaginatorAdapter($query, $options = array())
{
return $this->finder->createPaginatorAdapter($query);
return $this->finder->createPaginatorAdapter($query, $options);
}
}

View file

@ -2,6 +2,9 @@
namespace FOS\ElasticaBundle;
use Elastica\Exception\ExceptionInterface;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
/**
@ -26,8 +29,8 @@ class Resetter
*/
public function resetAllIndexes()
{
foreach ($this->indexConfigsByName as $indexConfig) {
$indexConfig['index']->create($indexConfig['config'], true);
foreach (array_keys($this->indexConfigsByName) as $name) {
$this->resetIndex($name);
}
}
@ -40,7 +43,17 @@ class Resetter
public function resetIndex($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$indexConfig['index']->create($indexConfig['config'], true);
$esIndex = $indexConfig['index'];
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$name = $indexConfig['name_or_alias'];
$name .= uniqid();
$esIndex->overrideName($name);
$esIndex->create($indexConfig['config']);
return;
}
$esIndex->create($indexConfig['config'], true);
}
/**
@ -49,6 +62,7 @@ class Resetter
* @param string $indexName
* @param string $typeName
* @throws \InvalidArgumentException if no index or type mapping exists for the given names
* @throws ResponseException
*/
public function resetIndexType($indexName, $typeName)
{
@ -59,7 +73,13 @@ class Resetter
}
$type = $indexConfig['index']->getType($typeName);
$type->delete();
try {
$type->delete();
} catch (ResponseException $e) {
if (strpos($e->getMessage(), 'TypeMissingException') === false) {
throw $e;
}
}
$mapping = $this->createMapping($indexConfig['config']['mappings'][$typeName]);
$type->setMapping($mapping);
}
@ -74,12 +94,15 @@ class Resetter
{
$mapping = Mapping::create($indexConfig['properties']);
if (isset($indexConfig['_parent'])) {
$mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type']));
$mappingSpecialFields = array('_uid', '_id', '_source', '_all', '_analyzer', '_boost', '_routing', '_index', '_size', '_timestamp', '_ttl', 'dynamic_templates');
foreach ($mappingSpecialFields as $specialField) {
if (isset($indexConfig[$specialField])) {
$mapping->setParam($specialField, $indexConfig[$specialField]);
}
}
if (isset($indexConfig['dynamic_templates'])) {
$mapping->setParam('dynamic_templates', $indexConfig['dynamic_templates']);
if (isset($indexConfig['_parent'])) {
$mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type']));
}
return $mapping;
@ -102,4 +125,122 @@ class Resetter
return $this->indexConfigsByName[$indexName];
}
public function postPopulate($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$this->switchIndexAlias($indexName);
}
}
/**
* Switches the alias for given index (by key) to the newly populated index
* and deletes the old index
*
* @param string $indexName Index name
*
* @throws \RuntimeException
*/
private function switchIndexAlias($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$esIndex = $indexConfig['index'];
$aliasName = $indexConfig['name_or_alias'];
$oldIndexName = false;
$newIndexName = $esIndex->getName();
$aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName);
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,
join(', ', $aliasedIndexes)
)
);
}
// Change the alias to point to the new index
// Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic
// In 0.20.x it's atomic, but it doesn't return the old index name
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) == 1) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName)
);
try {
$esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$esIndex->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getError()
);
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
)
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($esIndex->getClient(), $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
)
);
}
}
}
/**
* Returns array of indexes which are mapped to given alias
*
* @param Index $esIndex ES Index
* @param string $aliasName Alias name
*
* @return array
*/
private function getAliasedIndexes(Index $esIndex, $aliasName)
{
$aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
return $aliasedIndexes;
}
}

View file

@ -6,7 +6,7 @@
<parameters>
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Client</parameter>
<parameter key="fos_elastica.index.class">Elastica\Index</parameter>
<parameter key="fos_elastica.index.class">FOS\ElasticaBundle\DynamicIndex</parameter>
<parameter key="fos_elastica.type.class">Elastica\Type</parameter>
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
@ -14,6 +14,10 @@
<parameter key="fos_elastica.elastica_to_model_transformer.collection.class">FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
<parameter key="fos_elastica.provider_registry.class">FOS\ElasticaBundle\Provider\ProviderRegistry</parameter>
<parameter key="fos_elastica.property_accessor.class">Symfony\Component\PropertyAccess\PropertyAccessor</parameter>
<parameter key="fos_elastica.object_persister.class">FOS\ElasticaBundle\Persister\ObjectPersister</parameter>
<parameter key="fos_elastica.object_serializer_persister.class">FOS\ElasticaBundle\Persister\ObjectSerializerPersister</parameter>
<parameter key="fos_elastica.model_to_elastica_transformer.class">FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer</parameter>
<parameter key="fos_elastica.model_to_elastica_identifier_transformer.class">FOS\ElasticaBundle\Transformer\ModelToElasticaIdentifierTransformer</parameter>
</parameters>
<services>
@ -37,7 +41,7 @@
<argument /> <!-- index configs -->
</service>
<service id="fos_elastica.object_persister" class="FOS\ElasticaBundle\Persister\ObjectPersister" abstract="true">
<service id="fos_elastica.object_persister" class="%fos_elastica.object_persister.class%" abstract="true">
<argument /> <!-- type -->
<argument /> <!-- model to elastica transformer -->
<argument /> <!-- model -->
@ -49,21 +53,21 @@
<argument /> <!-- transformer -->
</service>
<service id="fos_elastica.object_serializer_persister" class="FOS\ElasticaBundle\Persister\ObjectSerializerPersister" abstract="true">
<service id="fos_elastica.object_serializer_persister" class="%fos_elastica.object_serializer_persister.class%" abstract="true">
<argument /> <!-- type -->
<argument /> <!-- model to elastica transformer -->
<argument /> <!-- model -->
<argument /> <!-- serializer -->
</service>
<service id="fos_elastica.model_to_elastica_transformer" class="FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer" public="false" abstract="true">
<service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true">
<argument /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.model_to_elastica_identifier_transformer" class="FOS\ElasticaBundle\Transformer\ModelToElasticaIdentifierTransformer" public="false" abstract="true">
<service id="fos_elastica.model_to_elastica_identifier_transformer" class="%fos_elastica.model_to_elastica_identifier_transformer.class%" public="false" abstract="true">
<argument /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />

View file

@ -13,7 +13,7 @@
<argument type="service" id="doctrine_mongodb" />
</service>
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Listener" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->

View file

@ -13,7 +13,7 @@
<argument type="service" id="doctrine" />
</service>
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Listener" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->

View file

@ -44,6 +44,7 @@
{% for key, query in collector.queries %}
<li class="{{ cycle(['odd', 'even'], loop.index) }}">
<strong>Path</strong>: {{ query.path }}<br />
<strong>Query</strong>: {{ query.queryString|url_encode }}<br />
<strong>Method</strong>: {{ query.method }} <small>({{ query.connection.transport }} on {{ query.connection.host }}:{{ query.connection.port }})</small>
<div>
<code>{{ query.data|json_encode }}</code>
@ -60,7 +61,7 @@
</a>
<div style="display: none;" id="elastica_curl_query_{{ key }}">
<code>curl -X{{ query.method }} '{{ query.connection.transport|lower }}://{{ query.connection.host }}:{{ query.connection.port }}/{{ query.path }}' -d '{{ query.data|json_encode }}'</code>
<code>curl -X{{ query.method }} '{{ query.connection.transport|lower }}://{{ query.connection.host }}:{{ query.connection.port }}/{{ query.path }}{% if query.queryString|length %}?{{ query.queryString|url_encode }}{% endif %}' -d '{{ query.data|json_encode }}'</code>
</div>
{% endif %}
</li>

43
Tests/ClientTest.php Normal file
View file

@ -0,0 +1,43 @@
<?php
namespace FOS\ElasticaBundle\Tests\Resetter;
use Elastica\Request;
use Elastica\Transport\Null as NullTransport;
class ClientTest extends \PHPUnit_Framework_TestCase
{
public function testRequestsAreLogged()
{
$transport = new NullTransport;
$connection = $this->getMock('Elastica\Connection');
$connection->expects($this->any())->method('getTransportObject')->will($this->returnValue($transport));
$connection->expects($this->any())->method('toArray')->will($this->returnValue(array()));
$logger = $this->getMock('FOS\ElasticaBundle\Logger\ElasticaLogger');
$logger
->expects($this->once())
->method('logQuery')
->with(
'foo',
Request::GET,
$this->isType('array'),
$this->isType('float'),
$this->isType('array'),
$this->isType('array')
);
$client = $this->getMockBuilder('FOS\ElasticaBundle\Client')
->setMethods(array('getConnection'))
->getMock();
$client->expects($this->any())->method('getConnection')->will($this->returnValue($connection));
$client->setLogger($logger);
$response = $client->request('foo');
$this->assertInstanceOf('Elastica\Response', $response);
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace FOS\ElasticaBundle\Tests\Command;
use FOS\ElasticaBundle\Command\ResetCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\DependencyInjection\Container;
class ResetCommandTest extends \PHPUnit_Framework_TestCase
{
private $resetter;
private $indexManager;
public function setup()
{
$container = new Container();
$this->resetter = $this->getMockBuilder('\FOS\ElasticaBundle\Resetter')
->disableOriginalConstructor()
->setMethods(array('resetIndex', 'resetIndexType'))
->getMock();
$container->set('fos_elastica.resetter', $this->resetter);
$this->indexManager = $this->getMockBuilder('\FOS\ElasticaBundle\IndexManager')
->disableOriginalConstructor()
->setMethods(array('getAllIndexes'))
->getMock();
$container->set('fos_elastica.index_manager', $this->indexManager);
$this->command = new ResetCommand();
$this->command->setContainer($container);
}
public function testResetAllIndexes()
{
$this->indexManager->expects($this->any())
->method('getAllIndexes')
->will($this->returnValue(array('index1' => true, 'index2' => true)));
$this->resetter->expects($this->at(0))
->method('resetIndex')
->with($this->equalTo('index1'));
$this->resetter->expects($this->at(1))
->method('resetIndex')
->with($this->equalTo('index2'));
$this->command->run(
new ArrayInput(array()),
new NullOutput()
);
}
public function testResetIndex()
{
$this->indexManager->expects($this->never())
->method('getAllIndexes');
$this->resetter->expects($this->at(0))
->method('resetIndex')
->with($this->equalTo('index1'));
$this->command->run(
new ArrayInput(array('--index' => 'index1')),
new NullOutput()
);
}
public function testResetIndexType()
{
$this->indexManager->expects($this->never())
->method('getAllIndexes');
$this->resetter->expects($this->never())
->method('resetIndex');
$this->resetter->expects($this->at(0))
->method('resetIndexType')
->with($this->equalTo('index1'), $this->equalTo('type1'));
$this->command->run(
new ArrayInput(array('--index' => 'index1', '--type' => 'type1')),
new NullOutput()
);
}
}

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Tests\Resetter\DependencyInjection;
use FOS\ElasticaBundle\DependencyInjection\Configuration;
use Symfony\Component\Config\Definition\Processor;
/**
* ConfigurationTest
@ -85,4 +86,51 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mapping['index']);
$this->assertNull($mapping['index']->getDefaultValue());
}
public function testSlashIsAddedAtTheEndOfServerUrl()
{
$config = array(
'clients' => array(
'default' => array(
'url' => 'http://www.github.com',
),
),
);
$processor = new Processor();
$configuration = $processor->processConfiguration($this->configuration, array($config));
$this->assertEquals('http://www.github.com/', $configuration['clients']['default']['servers'][0]['url']);
}
public function testEmptyFieldsIndexIsUnset()
{
$config = array(
'indexes' => array(
'test' => array(
'types' => array(
'test' => array(
'mappings' => array(
'title' => array(
'type' => 'string',
'fields' => array(
'autocomplete' => null
)
),
'content' => null
)
)
)
)
)
);
$processor = new Processor();
$configuration = $processor->processConfiguration(new Configuration(array($config)), array($config));
$this->assertArrayNotHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['content']);
$this->assertArrayHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['title']);
}
}

View file

@ -3,9 +3,11 @@
namespace FOS\ElasticaBundle\Tests\Doctrine;
/**
* See concrete MongoDB/ORM instances of this abstract test
*
* @author Richard Miller <info@limethinking.co.uk>
*/
abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
public function testObjectInsertedOnPersist()
{
@ -14,12 +16,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->once())
->method('insertOne')
->with($entity);
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
$persister->expects($this->once())
->method('insertMany')
->with($listener->scheduledForInsertion);
$listener->postFlush($eventArgs);
}
/**
@ -32,12 +38,18 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1, false);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->never())
->method('insertOne');
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
$persister->expects($this->never())
->method('insertOne');
$persister->expects($this->never())
->method('insertMany');
$listener->postFlush($eventArgs);
}
public function testObjectReplacedOnUpdate()
@ -47,15 +59,18 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->once())
->method('replaceOne')
->with($entity);
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
$persister->expects($this->once())
->method('replaceMany')
->with(array($entity));
$persister->expects($this->never())
->method('deleteById');
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postUpdate($eventArgs);
$listener->postFlush($eventArgs);
}
/**
@ -80,16 +95,20 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$persister->expects($this->never())
->method('replaceOne');
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->never())
->method('replaceOne');
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
public function testObjectDeletedOnRemove()
@ -111,13 +130,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array());
$listener->preRemove($eventArgs);
$listener->postRemove($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
public function testObjectWithNonStandardIdentifierDeletedOnRemove()
@ -139,13 +161,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array(), 'identifier');
$listener->preRemove($eventArgs);
$listener->postRemove($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
/**

View file

@ -135,6 +135,30 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($loggerClosureInvoked);
}
public function testPopulateNotStopOnError()
{
$nbObjects = 1;
$objects = array(1);
$provider = $this->getMockAbstractProvider();
$provider->expects($this->any())
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
->will($this->returnValue($objects));
$this->objectPersister->expects($this->any())
->method('insertMany')
->will($this->throwException($this->getMockBulkResponseException()));
$this->setExpectedException('Elastica\Exception\Bulk\ResponseException');
$provider->populate(null, array('ignore-errors' => false));
}
/**
* @return \FOS\ElasticaBundle\Doctrine\AbstractProvider|\PHPUnit_Framework_MockObject_MockObject
*/
@ -148,6 +172,16 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
));
}
/**
* @return \Elastica\Exception\Bulk\ResponseException
*/
private function getMockBulkResponseException()
{
return $this->getMockBuilder('Elastica\Exception\Bulk\ResponseException')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \Doctrine\Common\Persistence\ManagerRegistry|\PHPUnit_Framework_MockObject_MockObject
*/

View file

@ -2,9 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Doctrine\MongoDB;
use FOS\ElasticaBundle\Tests\Doctrine\AbstractListenerTest;
use FOS\ElasticaBundle\Tests\Doctrine\ListenerTest as BaseListenerTest;
class ListenerTest extends AbstractListenerTest
class ListenerTest extends BaseListenerTest
{
public function setUp()
{
@ -25,7 +25,7 @@ class ListenerTest extends AbstractListenerTest
protected function getListenerClass()
{
return 'FOS\ElasticaBundle\Doctrine\MongoDB\Listener';
return 'FOS\ElasticaBundle\Doctrine\Listener';
}
protected function getObjectManagerClass()

View file

@ -2,9 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Doctrine\ORM;
use FOS\ElasticaBundle\Tests\Doctrine\AbstractListenerTest;
use FOS\ElasticaBundle\Tests\Doctrine\ListenerTest as BaseListenerTest;
class ListenerTest extends AbstractListenerTest
class ListenerTest extends BaseListenerTest
{
public function setUp()
{
@ -25,7 +25,7 @@ class ListenerTest extends AbstractListenerTest
protected function getListenerClass()
{
return 'FOS\ElasticaBundle\Doctrine\ORM\Listener';
return 'FOS\ElasticaBundle\Doctrine\Listener';
}
protected function getObjectManagerClass()

View file

@ -0,0 +1,39 @@
<?php
namespace FOS\ElasticaBundle\Tests\Resetter;
use FOS\ElasticaBundle\FOSElasticaBundle;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
class FOSElasticaBundleTest extends \PHPUnit_Framework_TestCase
{
public function testCompilerPassesAreRegistered()
{
$passes = array(
array (
'FOS\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass',
PassConfig::TYPE_BEFORE_REMOVING
),
array (
'FOS\ElasticaBundle\DependencyInjection\Compiler\TransformerPass'
),
);
$container = $this
->getMock('Symfony\Component\DependencyInjection\ContainerBuilder');
$container
->expects($this->at(0))
->method('addCompilerPass')
->with($this->isInstanceOf($passes[0][0]), $passes[0][1]);
$container
->expects($this->at(1))
->method('addCompilerPass')
->with($this->isInstanceOf($passes[1][0]));
$bundle = new FOSElasticaBundle();
$bundle->build($container);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace FOS\ElasticaBundle\Tests\Resetter;
use Elastica\Result;
use FOS\ElasticaBundle\HybridResult;
class HybridResultTest extends \PHPUnit_Framework_TestCase
{
public function testTransformedResultDefaultsToNull()
{
$result = new Result(array());
$hybridResult = new HybridResult($result);
$this->assertSame($result, $hybridResult->getResult());
$this->assertNull($hybridResult->getTransformed());
}
}

View file

@ -9,6 +9,40 @@ use FOS\ElasticaBundle\Logger\ElasticaLogger;
*/
class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
{
/**
* @return \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpKernel\Log\LoggerInterface
*/
private function getMockLogger()
{
return $this->getMockBuilder('Symfony\Component\HttpKernel\Log\LoggerInterface')
->disableOriginalConstructor()
->getMock();
}
/**
* @param string $level
* @param string $message
* @param array $context
* @return ElasticaLogger
*/
private function getMockLoggerForLevelMessageAndContext($level, $message, $context)
{
$loggerMock = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\LoggerInterface')
->disableOriginalConstructor()
->getMock();
$loggerMock->expects($this->once())
->method($level)
->with(
$this->equalTo($message),
$this->equalTo($context)
);
$elasticaLogger = new ElasticaLogger($loggerMock);
return $elasticaLogger;
}
public function testGetZeroIfNoQueriesAdded()
{
$elasticaLogger = new ElasticaLogger;
@ -36,6 +70,7 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
$data = array('data');
$time = 12;
$connection = array('host' => 'localhost', 'port' => '8999', 'transport' => 'https');
$query = array('search_type' => 'dfs_query_then_fetch');
$expected = array(
'path' => $path,
@ -43,9 +78,10 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
'data' => $data,
'executionMS' => $time,
'connection' => $connection,
'queryString' => $query,
);
$elasticaLogger->logQuery($path, $method, $data, $time, $connection);
$elasticaLogger->logQuery($path, $method, $data, $time, $connection, $query);
$returnedQueries = $elasticaLogger->getQueries();
$this->assertEquals($expected, $returnedQueries[0]);
}
@ -64,10 +100,7 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
public function testQueryIsLogged()
{
/** @var $loggerMock \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\HttpKernel\Log\LoggerInterface */
$loggerMock = $this->getMockBuilder('Symfony\Component\HttpKernel\Log\LoggerInterface')
->disableOriginalConstructor()
->getMock();
$loggerMock = $this->getMockLogger();
$elasticaLogger = new ElasticaLogger($loggerMock);
@ -87,4 +120,55 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
$elasticaLogger->logQuery($path, $method, $data, $time);
}
/**
* @return array
*/
public function logLevels()
{
return array(
array('emergency'),
array('alert'),
array('critical'),
array('error'),
array('warning'),
array('notice'),
array('info'),
array('debug'),
);
}
/**
* @dataProvider logLevels
*/
public function testMessagesCanBeLoggedAtSpecificLogLevels($level)
{
$message = 'foo';
$context = array('data');
$loggerMock = $this->getMockLoggerForLevelMessageAndContext($level, $message, $context);
call_user_func(array($loggerMock, $level), $message, $context);
}
public function testMessagesCanBeLoggedToArbitraryLevels()
{
$loggerMock = $this->getMockLogger();
$level = 'info';
$message = 'foo';
$context = array('data');
$loggerMock->expects($this->once())
->method('log')
->with(
$level,
$message,
$context
);
$elasticaLogger = new ElasticaLogger($loggerMock);
$elasticaLogger->log($level, $message, $context);
}
}

View file

@ -58,6 +58,22 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
$repository->findPaginated($testQuery);
}
public function testThatCreatePaginatorCreatesAPaginatorViaFinder()
{
$testQuery = 'Test Query';
/** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
$finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
->disableOriginalConstructor()
->getMock();
$finderMock->expects($this->once())
->method('createPaginatorAdapter')
->with($this->equalTo($testQuery));
$repository = new Repository($finderMock);
$repository->createPaginatorAdapter($testQuery);
}
public function testThatFindHybridCallsFindHybridOnFinder()
{
$testQuery = 'Test Query';

View file

@ -2,6 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Resetter;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Resetter;
use Elastica\Type\Mapping;
@ -130,6 +133,32 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
$resetter->resetIndexType('foo', 'c');
}
public function testResetIndexTypeIgnoreTypeMissingException()
{
$type = $this->getMockElasticaType();
$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('getType')
->with('a')
->will($this->returnValue($type));
$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']['mappings']['a']['properties']);
$mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['mappings']['a']['dynamic_templates']);
$type->expects($this->once())
->method('setMapping')
->with($mapping);
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('foo', 'a');
}
public function testIndexMappingForParent()
{
$type = $this->getMockElasticaType();

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer;
use Elastica\Document;
use Elastica\Result;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection;
class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCase
@ -98,6 +99,57 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$result2,
), $results);
}
public function testGetIdentifierFieldReturnsAMapOfIdentifiers()
{
$collection = new ElasticaToModelTransformerCollection(array());
$identifiers = $collection->getIdentifierField();
$this->assertInternalType('array', $identifiers);
$this->assertEmpty($identifiers);
$this->collectionSetup();
$identifiers = $this->collection->getIdentifierField();
$this->assertInternalType('array', $identifiers);
$this->assertEquals(array('type1' => 'id', 'type2' => 'id'), $identifiers);
}
public function elasticaResults()
{
$result = new Result(array('_id' => 123, '_type' => 'type1'));
$transformedObject = new POPO(123, array());
return array(
array(
$result, $transformedObject
)
);
}
/**
* @dataProvider elasticaResults
*/
public function testHybridTransformDecoratesResultsWithHybridResultObjects($result, $transformedObject)
{
$transformer = $this->getMock('FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface');
$transformer->expects($this->any())->method('getIdentifierField')->will($this->returnValue('id'));
$transformer
->expects($this->any())
->method('transform')
->will($this->returnValue(array($transformedObject)));
$collection = new ElasticaToModelTransformerCollection(array('type1' => $transformer));
$hybridResults = $collection->hybridTransform(array($result));
$this->assertInternalType('array', $hybridResults);
$this->assertNotEmpty($hybridResults);
$this->assertContainsOnlyInstancesOf('FOS\ElasticaBundle\HybridResult', $hybridResults);
$hybridResult = array_pop($hybridResults);
$this->assertEquals($result, $hybridResult->getResult());
$this->assertEquals($transformedObject, $hybridResult->getTransformed());
}
}
class POPO

View file

@ -67,7 +67,9 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
$result = array();
foreach ($elasticaObjects as $object) {
$result[] = $transformed[$object->getType()][$object->getId()];
if (array_key_exists($object->getId(), $transformed[$object->getType()])) {
$result[] = $transformed[$object->getType()][$object->getId()];
}
}
return $result;
@ -84,11 +86,4 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
return $result;
}
protected function getTypeToClassMap()
{
return array_map(function (ElasticaToModelTransformerInterface $transformer) {
return $transformer->getObjectClass();
}, $this->transformers);
}
}

View file

@ -12,11 +12,11 @@
],
"require": {
"php": ">=5.3.2",
"symfony/framework-bundle": "~2.1",
"symfony/framework-bundle": "~2.3",
"symfony/console": "~2.1",
"symfony/form": "~2.1",
"symfony/property-access": "~2.2",
"ruflin/elastica": "~0.20",
"ruflin/elastica": ">=0.20, <1.1-dev",
"psr/log": "~1.0"
},
"require-dev":{