diff --git a/.travis.yml b/.travis.yml
index 02f3ab8..fbb22d1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,12 @@
language: php
+cache:
+ directories:
+ - $HOME/.composer/cache
+
php:
- 5.3
+ - 5.4
- 5.5
- 5.6
@@ -11,8 +16,6 @@ matrix:
env: SYMFONY_VERSION='2.3.*'
- php: 5.5
env: SYMFONY_VERSION='2.5.*'
- - php: 5.5
- env: SYMFONY_VERSION='dev-master'
before_script:
- /usr/share/elasticsearch/bin/elasticsearch -v
@@ -22,7 +25,8 @@ before_script:
- sh -c 'if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi;'
- composer install --dev --prefer-source
-script: vendor/bin/phpunit --coverage-clover=coverage.clover
+script:
+ - vendor/bin/phpunit --coverage-clover=coverage.clover
services:
- elasticsearch
diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md
index 2596a29..973269d 100644
--- a/CHANGELOG-3.0.md
+++ b/CHANGELOG-3.0.md
@@ -12,6 +12,24 @@ 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.8 (2014-01-31)
+
+ * Fixed handling of empty indexes #760
+ * Added support for `connectionStrategy` Elastica configuration #732
+ * Allow Elastica 1.4
+
+* 3.0.7 (2015-01-21)
+
+ * Fixed the indexing of parent/child relations, broken since 3.0 #774
+ * Fixed multi_field properties not being normalised #769
+
+* 3.0.6 (2015-01-04)
+
+ * Removed unused public image asset for the web development toolbar #742
+ * Fixed is_indexable_callback BC code to support array notation #761
+ * Fixed debug_logger for type providers #724
+ * Clean the OM if we filter away the entire batch #737
+
* 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called
diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md
index ee9af70..5dc440f 100644
--- a/CHANGELOG-3.1.md
+++ b/CHANGELOG-3.1.md
@@ -1,4 +1,4 @@
-CHANGELOG for 3.0.x
+CHANGELOG for 3.1.x
===================
This changelog references the relevant changes (bug and security fixes) done
@@ -9,8 +9,27 @@ 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.4...v3.1.0
-* 3.1.0
+* 3.1.0 (Unreleased)
-* BC BREAK: `DoctrineListener#scheduleForDeletion` access changed to private.
-* BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
- returns a boolean value if it will handle a given object or not.
+ * BC BREAK: `Doctrine\Listener#scheduleForDeletion` access changed to private.
+ * BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
+ returns a boolean value if it will handle a given object or not.
+ * BC BREAK: Removed `Doctrine\Listener#getSubscribedEvents`. The container
+ configuration now configures tags with the methods to call to avoid loading
+ this class on every request where doctrine is active. #729
+ * BC BREAK: Added methods for retrieving aggregations when paginating results.
+ The `PaginationAdapterInterface` has a new method, `getAggregations`. #726
+ * Added ability to configure `date_detection`, `numeric_detection` and
+ `dynamic_date_formats` for types. #753
+ * New event `POST_TRANSFORM` which allows developers to add custom properties to
+ Elastica Documents for indexing.
+ * When available, the `fos:elastica:populate` command will now use the
+ ProgressBar helper instead of outputting strings. You can use verbosity
+ controls on the command to output additional information like memory
+ usage, runtime and estimated time.
+ * Added new option `property_path` to a type property definition to allow
+ customisation of the property path used to retrieve data from objects.
+ Setting `property_path` to `false` will configure the Transformer to ignore
+ that property while transforming. Combined with the above POST_TRANSFORM event
+ developers can now create calculated dynamic properties on Elastica documents
+ for indexing. #794
diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php
index f17ca4c..5817c4c 100644
--- a/Command/PopulateCommand.php
+++ b/Command/PopulateCommand.php
@@ -11,6 +11,7 @@ use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Provider\ProviderRegistry;
use FOS\ElasticaBundle\Resetter;
use FOS\ElasticaBundle\Provider\ProviderInterface;
+use Symfony\Component\Console\Helper\ProgressBar;
/**
* Populate the search index
@@ -99,6 +100,82 @@ class PopulateCommand extends ContainerAwareCommand
}
}
+ /**
+ * @param ProviderInterface $provider
+ * @param OutputInterface $output
+ * @param string $input
+ * @param string $type
+ * @param array $options
+ */
+ private function doPopulateType(ProviderInterface $provider, OutputInterface $output, $input, $type, $options)
+ {
+ $loggerClosure = $this->getLoggerClosure($output, $input, $type);
+
+ $provider->populate($loggerClosure, $options);
+ }
+
+ /**
+ * Builds a loggerClosure to be called from inside the Provider to update the command
+ * line.
+ *
+ * @param OutputInterface $output
+ * @param string $index
+ * @param string $type
+ * @return callable
+ */
+ private function getLoggerClosure(OutputInterface $output, $index, $type)
+ {
+ if (!class_exists('Symfony\Component\Console\Helper\ProgressBar')) {
+ $lastStep = null;
+ $current = 0;
+
+ return function ($increment, $totalObjects) use ($output, $index, $type, &$lastStep, &$current) {
+ if ($current + $increment > $totalObjects) {
+ $increment = $totalObjects - $current;
+ }
+
+ $currentTime = microtime(true);
+ $timeDifference = $currentTime - $lastStep;
+ $objectsPerSecond = $lastStep ? ($increment / $timeDifference) : $increment;
+ $lastStep = $currentTime;
+ $current += $increment;
+ $percent = 100 * $current / $totalObjects;
+
+ $output->writeln(sprintf(
+ 'Populating %s/%s %0.1f%% (%d/%d), %d objects/s (RAM: current=%uMo peak=%uMo)',
+ $index,
+ $type,
+ $percent,
+ $current,
+ $totalObjects,
+ $objectsPerSecond,
+ round(memory_get_usage() / (1024 * 1024)),
+ round(memory_get_peak_usage() / (1024 * 1024))
+ ));
+ };
+ }
+
+ ProgressBar::setFormatDefinition('normal', " %current%/%max% [%bar%] %percent:3s%%\n%message%");
+ ProgressBar::setFormatDefinition('verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%\n%message%");
+ ProgressBar::setFormatDefinition('very_verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n%message%");
+ ProgressBar::setFormatDefinition('debug', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%\n%message%");
+ $progress = null;
+
+ return function ($increment, $totalObjects) use (&$progress, $output, $index, $type) {
+ if (null === $progress) {
+ $progress = new ProgressBar($output, $totalObjects);
+ $progress->start();
+ }
+
+ $progress->setMessage(sprintf('Populating %s/%s', $index, $type));
+ $progress->advance($increment);
+
+ if ($progress->getProgressPercent() >= 1.0) {
+ $progress->finish();
+ }
+ };
+ }
+
/**
* Recreates an index, populates its types, and refreshes the index.
*
@@ -118,16 +195,10 @@ class PopulateCommand extends ContainerAwareCommand
$providers = $this->providerRegistry->getIndexProviders($index);
foreach ($providers as $type => $provider) {
- $loggerClosure = function($message) use ($output, $index, $type) {
- $output->writeln(sprintf('Populating %s/%s, %s', $index, $type, $message));
- };
-
- $provider->populate($loggerClosure, $options);
+ $this->doPopulateType($provider, $output, $index, $type, $options);
}
- $output->writeln(sprintf('Refreshing %s', $index));
- $this->resetter->postPopulate($index);
- $this->indexManager->getIndex($index)->refresh();
+ $this->refreshIndex($output, $index);
}
/**
@@ -146,12 +217,24 @@ class PopulateCommand extends ContainerAwareCommand
$this->resetter->resetIndexType($index, $type);
}
- $loggerClosure = function($message) use ($output, $index, $type) {
- $output->writeln(sprintf('Populating %s/%s, %s', $index, $type, $message));
- };
-
$provider = $this->providerRegistry->getProvider($index, $type);
- $provider->populate($loggerClosure, $options);
+ $this->doPopulateType($provider, $output, $index, $type, $options);
+
+ $this->refreshIndex($output, $index, false);
+ }
+
+ /**
+ * Refreshes an index.
+ *
+ * @param OutputInterface $output
+ * @param string $index
+ * @param bool $postPopulate
+ */
+ private function refreshIndex(OutputInterface $output, $index, $postPopulate = true)
+ {
+ if ($postPopulate) {
+ $this->resetter->postPopulate($index);
+ }
$output->writeln(sprintf('Refreshing %s', $index));
$this->indexManager->getIndex($index)->refresh();
diff --git a/Configuration/Source/ContainerSource.php b/Configuration/Source/ContainerSource.php
index 8d094c7..abcdf1b 100644
--- a/Configuration/Source/ContainerSource.php
+++ b/Configuration/Source/ContainerSource.php
@@ -40,16 +40,7 @@ class ContainerSource implements SourceInterface
{
$indexes = array();
foreach ($this->configArray as $config) {
- $types = array();
- foreach ($config['types'] as $typeConfig) {
- $types[$typeConfig['name']] = new TypeConfig(
- $typeConfig['name'],
- $typeConfig['mapping'],
- $typeConfig['config']
- );
- // TODO: handle prototypes..
- }
-
+ $types = $this->getTypes($config);
$index = new IndexConfig($config['name'], $types, array(
'elasticSearchName' => $config['elasticsearch_name'],
'settings' => $config['settings'],
@@ -61,4 +52,28 @@ class ContainerSource implements SourceInterface
return $indexes;
}
+
+ /**
+ * Builds TypeConfig objects for each type.
+ *
+ * @param array $config
+ * @return array
+ */
+ protected function getTypes($config)
+ {
+ $types = array();
+
+ if (isset($config['types'])) {
+ foreach ($config['types'] as $typeConfig) {
+ $types[$typeConfig['name']] = new TypeConfig(
+ $typeConfig['name'],
+ $typeConfig['mapping'],
+ $typeConfig['config']
+ );
+ // TODO: handle prototypes..
+ }
+ }
+
+ return $types;
+ }
}
diff --git a/Configuration/TypeConfig.php b/Configuration/TypeConfig.php
index fc9041d..a46cd34 100644
--- a/Configuration/TypeConfig.php
+++ b/Configuration/TypeConfig.php
@@ -35,6 +35,22 @@ class TypeConfig
$this->name = $name;
}
+ /**
+ * @return bool|null
+ */
+ public function getDateDetection()
+ {
+ return $this->getConfig('date_detection');
+ }
+
+ /**
+ * @return array
+ */
+ public function getDynamicDateFormats()
+ {
+ return $this->getConfig('dynamic_date_formats');
+ }
+
/**
* @return string|null
*/
@@ -61,6 +77,14 @@ class TypeConfig
null;
}
+ /**
+ * @return bool|null
+ */
+ public function getNumericDetection()
+ {
+ return $this->getConfig('numeric_detection');
+ }
+
/**
* @return string
*/
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 3c5d18c..114d09b 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -84,6 +84,16 @@ class Configuration implements ConfigurationInterface
return $v;
})
->end()
+ // Elastica names its properties with camel case, support both
+ ->beforeNormalization()
+ ->ifTrue(function ($v) { return isset($v['connection_strategy']); })
+ ->then(function ($v) {
+ $v['connectionStrategy'] = $v['connection_strategy'];
+ unset($v['connection_strategy']);
+
+ return $v;
+ })
+ ->end()
// If there is no connections array key defined, assume a single connection.
->beforeNormalization()
->ifTrue(function ($v) { return is_array($v) && !array_key_exists('connections', $v); })
@@ -124,6 +134,7 @@ class Configuration implements ConfigurationInterface
->end()
->scalarNode('timeout')->end()
->scalarNode('headers')->end()
+ ->scalarNode('connectionStrategy')->defaultValue('Simple')->end()
->end()
->end()
->end()
@@ -181,6 +192,10 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('name')
->prototype('array')
->treatNullLike(array())
+ ->beforeNormalization()
+ ->ifNull()
+ ->thenEmptyArray()
+ ->end()
// BC - Renaming 'mappings' node to 'properties'
->beforeNormalization()
->ifTrue(function($v) { return array_key_exists('mappings', $v); })
@@ -199,7 +214,17 @@ class Configuration implements ConfigurationInterface
isset($v['persistence']['listener']['is_indexable_callback']);
})
->then(function ($v) {
- $v['indexable_callback'] = $v['persistence']['listener']['is_indexable_callback'];
+ $callback = $v['persistence']['listener']['is_indexable_callback'];
+
+ if (is_array($callback)) {
+ list($class) = $callback + array(null);
+
+ if ($class[0] !== '@' && is_string($class) && !class_exists($class)) {
+ $callback[0] = '@'.$class;
+ }
+ }
+
+ $v['indexable_callback'] = $callback;
unset($v['persistence']['listener']['is_indexable_callback']);
return $v;
@@ -225,7 +250,10 @@ class Configuration implements ConfigurationInterface
})
->end()
->children()
+ ->booleanNode('date_detection')->end()
+ ->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end()
->scalarNode('index_analyzer')->end()
+ ->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end()
->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode())
@@ -480,9 +508,13 @@ class Configuration implements ConfigurationInterface
->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('debug_logging')
+ ->defaultValue($this->debug)
+ ->treatNullLike(true)
+ ->end()
+ ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php
index 804be44..9c1912e 100644
--- a/DependencyInjection/FOSElasticaExtension.php
+++ b/DependencyInjection/FOSElasticaExtension.php
@@ -241,6 +241,9 @@ class FOSElasticaExtension extends Extension
'serializer',
'index_analyzer',
'search_analyzer',
+ 'date_detection',
+ 'dynamic_date_formats',
+ 'numeric_detection',
) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] :
@@ -393,7 +396,12 @@ class FOSElasticaExtension extends Extension
$arguments[] = array(new Reference($callbackId), 'serialize');
} else {
$abstractId = 'fos_elastica.object_persister';
- $arguments[] = $this->indexConfigs[$indexName]['types'][$typeName]['mapping']['properties'];
+ $mapping = $this->indexConfigs[$indexName]['types'][$typeName]['mapping'];
+ $argument = $mapping['properties'];
+ if(isset($mapping['_parent'])){
+ $argument['_parent'] = $mapping['_parent'];
+ }
+ $arguments[] = $argument;
}
$serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName);
@@ -464,19 +472,29 @@ class FOSElasticaExtension extends Extension
$listenerId = sprintf('fos_elastica.listener.%s.%s', $indexName, $typeName);
$listenerDef = new DefinitionDecorator($abstractListenerId);
$listenerDef->replaceArgument(0, new Reference($objectPersisterId));
- $listenerDef->replaceArgument(1, $this->getDoctrineEvents($typeConfig));
- $listenerDef->replaceArgument(3, array(
+ $listenerDef->replaceArgument(2, array(
'identifier' => $typeConfig['identifier'],
'indexName' => $indexName,
'typeName' => $typeName,
));
if ($typeConfig['listener']['logger']) {
- $listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
+ $listenerDef->replaceArgument(3, new Reference($typeConfig['listener']['logger']));
}
+ $tagName = null;
switch ($typeConfig['driver']) {
- case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
- case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
+ case 'orm':
+ $tagName = 'doctrine.event_listener';
+ break;
+ case 'mongodb':
+ $tagName = 'doctrine_mongodb.odm.event_listener';
+ break;
+ }
+
+ if ($tagName) {
+ foreach ($this->getDoctrineEvents($typeConfig) as $event) {
+ $listenerDef->addTag($tagName, array('event' => $event));
+ }
}
$container->setDefinition($listenerId, $listenerDef);
diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php
index b4799fd..451b9b5 100644
--- a/Doctrine/AbstractProvider.php
+++ b/Doctrine/AbstractProvider.php
@@ -43,7 +43,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
+ * {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
@@ -58,49 +58,22 @@ abstract class AbstractProvider extends BaseAbstractProvider
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
$ignoreErrors = isset($options['ignore-errors']) ? $options['ignore-errors'] : $this->options['ignore_errors'];
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
+
$objects = array();
-
for (; $offset < $nbObjects; $offset += $batchSize) {
- if ($loggerClosure) {
- $stepStartTime = microtime(true);
- }
-
- if ($this->sliceFetcher) {
- $identifierFieldNames = $manager
- ->getClassMetadata($this->objectClass)
- ->getIdentifierFieldNames();
-
- $objects = $this->sliceFetcher->fetch(
- $queryBuilder,
- $batchSize,
- $offset,
- $objects,
- $identifierFieldNames
- );
- } else {
- $objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
- }
-
- if ($loggerClosure) {
- $stepNbObjects = count($objects);
- }
+ $objects = $this->getSlice($queryBuilder, $batchSize, $offset, $objects);
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
- if (!$objects) {
- if ($loggerClosure) {
- $loggerClosure('Entire batch was filtered away, skipping...');
- }
- continue;
- }
-
- if (!$ignoreErrors) {
- $this->objectPersister->insertMany($objects);
- } else {
- try {
+ if ($objects) {
+ if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
- } catch(BulkResponseException $e) {
- if ($loggerClosure) {
- $loggerClosure(sprintf('%s',$e->getMessage()));
+ } else {
+ try {
+ $this->objectPersister->insertMany($objects);
+ } catch(BulkResponseException $e) {
+ if ($loggerClosure) {
+ $loggerClosure($batchSize, $nbObjects, sprintf('%s',$e->getMessage()));
+ }
}
}
}
@@ -112,11 +85,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
usleep($sleep);
if ($loggerClosure) {
- $stepCount = $stepNbObjects + $offset;
- $percentComplete = 100 * $stepCount / $nbObjects;
- $timeDifference = microtime(true) - $stepStartTime;
- $objectsPerSecond = $timeDifference ? ($stepNbObjects / $timeDifference) : $stepNbObjects;
- $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
+ $loggerClosure($batchSize, $nbObjects);
}
}
@@ -125,6 +94,36 @@ abstract class AbstractProvider extends BaseAbstractProvider
}
}
+ /**
+ * If this Provider has a SliceFetcher defined, we use it instead of falling back to
+ * the fetchSlice methods defined in the ORM/MongoDB subclasses.
+ *
+ * @param $queryBuilder
+ * @param int $limit
+ * @param int $offset
+ * @param array $lastSlice
+ * @return array
+ */
+ protected function getSlice($queryBuilder, $limit, $offset, $lastSlice)
+ {
+ if (!$this->sliceFetcher) {
+ return $this->fetchSlice($queryBuilder, $limit, $offset);
+ }
+
+ $manager = $this->managerRegistry->getManagerForClass($this->objectClass);
+ $identifierFieldNames = $manager
+ ->getClassMetadata($this->objectClass)
+ ->getIdentifierFieldNames();
+
+ return $this->sliceFetcher->fetch(
+ $queryBuilder,
+ $limit,
+ $offset,
+ $lastSlice,
+ $identifierFieldNames
+ );
+ }
+
/**
* Counts objects that would be indexed using the query builder.
*
diff --git a/Doctrine/Listener.php b/Doctrine/Listener.php
index 039ddaa..aabee53 100644
--- a/Doctrine/Listener.php
+++ b/Doctrine/Listener.php
@@ -2,7 +2,6 @@
namespace FOS\ElasticaBundle\Doctrine;
-use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
@@ -14,7 +13,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
* Automatically update ElasticSearch based on changes to the Doctrine source
* data. One listener is generated for each Doctrine entity / ElasticSearch type.
*/
-class Listener implements EventSubscriber
+class Listener
{
/**
* Object persister
@@ -23,13 +22,6 @@ class Listener implements EventSubscriber
*/
protected $objectPersister;
- /**
- * List of subscribed events
- *
- * @var array
- */
- protected $events;
-
/**
* Configuration for the listener
*
@@ -74,14 +66,12 @@ class Listener implements EventSubscriber
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param array $events
* @param IndexableInterface $indexable
* @param array $config
* @param null $logger
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
- array $events,
IndexableInterface $indexable,
array $config = array(),
$logger = null
@@ -89,7 +79,6 @@ class Listener implements EventSubscriber
$this->config = array_merge(array(
'identifier' => 'id',
), $config);
- $this->events = $events;
$this->indexable = $indexable;
$this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
@@ -99,14 +88,6 @@ class Listener implements EventSubscriber
}
}
- /**
- * @see Doctrine\Common\EventSubscriber::getSubscribedEvents()
- */
- public function getSubscribedEvents()
- {
- return $this->events;
- }
-
/**
* Looks for new objects that should be indexed.
*
diff --git a/Event/TransformEvent.php b/Event/TransformEvent.php
new file mode 100644
index 0000000..4f6871f
--- /dev/null
+++ b/Event/TransformEvent.php
@@ -0,0 +1,78 @@
+
+ *
+ * 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 TransformEvent extends Event
+{
+ const POST_TRANSFORM = 'fos_elastica.post_transform';
+
+ /**
+ * @var mixed
+ */
+ private $document;
+
+ /**
+ * @var array
+ */
+ private $fields;
+
+ /**
+ * @var mixed
+ */
+ private $object;
+
+ /**
+ * @param mixed $document
+ * @param array $fields
+ * @param mixed $object
+ */
+ public function __construct($document, array $fields, $object)
+ {
+ $this->document = $document;
+ $this->fields = $fields;
+ $this->object = $object;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFields()
+ {
+ return $this->fields;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * @param mixed $document
+ */
+ public function setDocument($document)
+ {
+ $this->document = $document;
+ }
+}
diff --git a/Index/MappingBuilder.php b/Index/MappingBuilder.php
index 21ae871..92beaf7 100644
--- a/Index/MappingBuilder.php
+++ b/Index/MappingBuilder.php
@@ -58,13 +58,19 @@ class MappingBuilder
*/
public function buildTypeMapping(TypeConfig $typeConfig)
{
- $mapping = array_merge($typeConfig->getMapping(), array(
- // 'date_detection' => true,
- // 'dynamic_date_formats' => array()
- // 'dynamic_templates' => $typeConfig->getDynamicTemplates(),
- // 'numeric_detection' => false,
- // 'properties' => array(),
- ));
+ $mapping = $typeConfig->getMapping();
+
+ if (null !== $typeConfig->getDynamicDateFormats()) {
+ $mapping['dynamic_date_formats'] = $typeConfig->getDynamicDateFormats();
+ }
+
+ if (null !== $typeConfig->getDateDetection()) {
+ $mapping['date_detection'] = $typeConfig->getDateDetection();
+ }
+
+ if (null !== $typeConfig->getNumericDetection()) {
+ $mapping['numeric_detection'] = $typeConfig->getNumericDetection();
+ }
if ($typeConfig->getIndexAnalyzer()) {
$mapping['index_analyzer'] = $typeConfig->getIndexAnalyzer();
@@ -104,9 +110,14 @@ class MappingBuilder
private function fixProperties(&$properties)
{
foreach ($properties as $name => &$property) {
+ unset($property['property_path']);
+
if (!isset($property['type'])) {
$property['type'] = 'string';
}
+ if ($property['type'] == 'multi_field' && isset($property['fields'])) {
+ $this->fixProperties($property['fields']);
+ }
if (isset($property['properties'])) {
$this->fixProperties($property['properties']);
}
diff --git a/Index/Resetter.php b/Index/Resetter.php
index 9b65a8f..68a43dd 100644
--- a/Index/Resetter.php
+++ b/Index/Resetter.php
@@ -42,6 +42,9 @@ class Resetter
/**
* Deletes and recreates all indexes
+ *
+ * @param bool $populating
+ * @param bool $force
*/
public function resetAllIndexes($populating = false, $force = false)
{
diff --git a/Paginator/FantaPaginatorAdapter.php b/Paginator/FantaPaginatorAdapter.php
index 2ad6983..812afa8 100644
--- a/Paginator/FantaPaginatorAdapter.php
+++ b/Paginator/FantaPaginatorAdapter.php
@@ -39,6 +39,18 @@ class FantaPaginatorAdapter implements AdapterInterface
{
return $this->adapter->getFacets();
}
+
+ /**
+ * Returns Aggregations
+ *
+ * @return mixed
+ *
+ * @api
+ */
+ public function getAggregations()
+ {
+ return $this->adapter->getAggregations();
+ }
/**
* Returns a slice of the results.
diff --git a/Paginator/PaginatorAdapterInterface.php b/Paginator/PaginatorAdapterInterface.php
index 25786a0..65271f0 100644
--- a/Paginator/PaginatorAdapterInterface.php
+++ b/Paginator/PaginatorAdapterInterface.php
@@ -31,4 +31,11 @@ interface PaginatorAdapterInterface
* @return mixed
*/
function getFacets();
+
+ /**
+ * Returns Aggregations
+ *
+ * @return mixed
+ */
+ function getAggregations();
}
diff --git a/Paginator/PartialResultsInterface.php b/Paginator/PartialResultsInterface.php
index 9efe7f3..1b0ae60 100644
--- a/Paginator/PartialResultsInterface.php
+++ b/Paginator/PartialResultsInterface.php
@@ -28,4 +28,11 @@ interface PartialResultsInterface
* @return array
*/
function getFacets();
-}
\ No newline at end of file
+
+ /**
+ * Returns the aggregations
+ *
+ * @return array
+ */
+ function getAggregations();
+}
diff --git a/Paginator/RawPaginatorAdapter.php b/Paginator/RawPaginatorAdapter.php
index f05205a..10b4a1b 100644
--- a/Paginator/RawPaginatorAdapter.php
+++ b/Paginator/RawPaginatorAdapter.php
@@ -36,6 +36,11 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
* @var array for the facets
*/
private $facets;
+
+ /**
+ * @var array for the aggregations
+ */
+ private $aggregations;
/**
* @see PaginatorAdapterInterface::__construct
@@ -82,6 +87,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
+ $this->aggregations = $resultSet->getAggregations();
return $resultSet;
}
@@ -100,15 +106,18 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the number of results.
*
+ * @param boolean $genuineTotal make the function return the `hits.total`
+ * value of the search result in all cases, instead of limiting it to the
+ * `size` request parameter.
* @return integer The number of results.
*/
- public function getTotalHits()
+ public function getTotalHits($genuineTotal = false)
{
if ( ! isset($this->totalHits)) {
$this->totalHits = $this->searchable->search($this->query)->getTotalHits();
}
- return $this->query->hasParam('size')
+ return $this->query->hasParam('size') && !$genuineTotal
? min($this->totalHits, (integer) $this->query->getParam('size'))
: $this->totalHits;
}
@@ -126,6 +135,19 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
return $this->facets;
}
+
+ /**
+ * Returns Aggregations
+ *
+ * @return mixed
+ */
+ public function getAggregations() {
+ if (!isset($this->aggregations)) {
+ $this->aggregations = $this->searchable->search($this->query)->getAggregations();
+ }
+
+ return $this->aggregations;
+ }
/**
* Returns the Query
diff --git a/Paginator/RawPartialResults.php b/Paginator/RawPartialResults.php
index a4afb00..908986f 100644
--- a/Paginator/RawPartialResults.php
+++ b/Paginator/RawPartialResults.php
@@ -49,4 +49,16 @@ class RawPartialResults implements PartialResultsInterface
return null;
}
-}
\ No newline at end of file
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAggregations()
+ {
+ if ($this->resultSet->hasAggregations()) {
+ return $this->resultSet->getAggregations();
+ }
+
+ return null;
+ }
+}
diff --git a/Propel/Provider.php b/Propel/Provider.php
index 38f7a61..a3af1bd 100644
--- a/Propel/Provider.php
+++ b/Propel/Provider.php
@@ -12,7 +12,7 @@ use FOS\ElasticaBundle\Provider\AbstractProvider;
class Provider extends AbstractProvider
{
/**
- * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
+ * {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
@@ -23,34 +23,21 @@ class Provider extends AbstractProvider
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
for (; $offset < $nbObjects; $offset += $batchSize) {
- if ($loggerClosure) {
- $stepStartTime = microtime(true);
- }
-
$objects = $queryClass::create()
->limit($batchSize)
->offset($offset)
->find()
->getArrayCopy();
- if ($loggerClosure) {
- $stepNbObjects = count($objects);
- }
+
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
- if (!$objects) {
- $loggerClosure('Entire batch was filtered away, skipping...');
-
- continue;
+ if ($objects) {
+ $this->objectPersister->insertMany($objects);
}
- $this->objectPersister->insertMany($objects);
-
usleep($sleep);
if ($loggerClosure) {
- $stepCount = $stepNbObjects + $offset;
- $percentComplete = 100 * $stepCount / $nbObjects;
- $objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
- $loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
+ $loggerClosure($batchSize, $nbObjects);
}
}
}
diff --git a/Provider/AbstractProvider.php b/Provider/AbstractProvider.php
index 82ea914..842518d 100644
--- a/Provider/AbstractProvider.php
+++ b/Provider/AbstractProvider.php
@@ -70,6 +70,7 @@ abstract class AbstractProvider implements ProviderInterface
/**
* Get string with RAM usage information (current and peak)
*
+ * @deprecated To be removed in 4.0
* @return string
*/
protected function getMemoryUsage()
diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml
index 84baf3e..07161e9 100644
--- a/Resources/config/mongodb.xml
+++ b/Resources/config/mongodb.xml
@@ -27,7 +27,6 @@
-
diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml
index ea95245..19a2bf5 100644
--- a/Resources/config/orm.xml
+++ b/Resources/config/orm.xml
@@ -27,7 +27,6 @@
-
diff --git a/Resources/config/transformer.xml b/Resources/config/transformer.xml
index 4ce5062..0957152 100644
--- a/Resources/config/transformer.xml
+++ b/Resources/config/transformer.xml
@@ -12,7 +12,8 @@
-
+
+
diff --git a/Resources/doc/cookbook/custom-properties.md b/Resources/doc/cookbook/custom-properties.md
new file mode 100644
index 0000000..1d7687e
--- /dev/null
+++ b/Resources/doc/cookbook/custom-properties.md
@@ -0,0 +1,33 @@
+##### Custom Properties
+
+Since FOSElasticaBundle 3.1.0, we now dispatch an event for each transformation of an
+object into an Elastica document which allows you to set custom properties on the Elastica
+document for indexing.
+
+Set up an event listener or subscriber for
+`FOS\ElasticaBundle\Event\TransformEvent::POST_TRANSFORM` to be able to inject your own
+parameters.
+
+```php
+class CustomPropertyListener implements EventSubscriberInterface
+{
+ private $anotherService;
+
+ // ...
+
+ public function addCustomProperty(TransformEvent $event)
+ {
+ $document = $event->getDocument();
+ $custom = $this->anotherService->calculateCustom($event->getObject());
+
+ $document->set('custom', $custom);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ TransformEvent::POST_TRANSFORM => 'addCustomProperty',
+ );
+ }
+}
+```
diff --git a/Resources/doc/cookbook/custom-repositories.md b/Resources/doc/cookbook/custom-repositories.md
index 9eff5f7..866f72d 100644
--- a/Resources/doc/cookbook/custom-repositories.md
+++ b/Resources/doc/cookbook/custom-repositories.md
@@ -4,7 +4,7 @@ As well as the default repository you can create a custom repository for an enti
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
-```
+```php
get('fos_elastica.manager');
+```php
+/** var FOS\ElasticaBundle\Manager\RepositoryManager */
+$repositoryManager = $container->get('fos_elastica.manager');
- /** var FOS\ElasticaBundle\Repository */
- $repository = $repositoryManager->getRepository('UserBundle:User');
+/** var FOS\ElasticaBundle\Repository */
+$repository = $repositoryManager->getRepository('UserBundle:User');
- /** var array of Acme\UserBundle\Entity\User */
- $users = $repository->findWithCustomQuery('bob');
+/** var array of Acme\UserBundle\Entity\User */
+$users = $repository->findWithCustomQuery('bob');
+```
Alternatively you can specify the custom repository using an annotation in the entity:
-```
+```php
indexableUsername`, and the indexed field `firstName` would be populated from a
+key `first` from an array on `User->names`.
+
+Setting the property path to `false` will disable transformation of that value. In this
+case the mapping will be created but no value will be populated while indexing. You can
+populate this value by listening to the `POST_TRANSFORM` event emitted by this bundle.
+See [cookbook/custom-properties.md](cookbook/custom-properties.md) for more information
+about this event.
+
Handling missing results with FOSElasticaBundle
-----------------------------------------------
diff --git a/Resources/doc/usage.md b/Resources/doc/usage.md
index 37514b3..be11dbf 100644
--- a/Resources/doc/usage.md
+++ b/Resources/doc/usage.md
@@ -26,7 +26,9 @@ $userPaginator = $finder->findPaginated('bob');
$countOfResults = $userPaginator->getNbResults();
// Option 3b. KnpPaginator resultset
-
+$paginator = $this->get('knp_paginator');
+$results = $finder->createPaginatorAdapter('bob');
+$pagination = $paginator->paginate($results, $page, 10);
```
Faceted Searching
@@ -160,7 +162,7 @@ fos_elastica:
site:
settings:
index:
- analysis:
+ analysis:
analyzer:
my_analyzer:
type: snowball
diff --git a/Resources/public/images/elastica.png b/Resources/public/images/elastica.png
deleted file mode 100644
index dbde014..0000000
Binary files a/Resources/public/images/elastica.png and /dev/null differ
diff --git a/Resources/views/Collector/elastica.html.twig b/Resources/views/Collector/elastica.html.twig
index e6d7072..82a3dcf 100644
--- a/Resources/views/Collector/elastica.html.twig
+++ b/Resources/views/Collector/elastica.html.twig
@@ -23,7 +23,7 @@
{% block menu %}
-
+
Elastica
{{ collector.querycount }}
diff --git a/Subscriber/PaginateElasticaQuerySubscriber.php b/Subscriber/PaginateElasticaQuerySubscriber.php
index 0b7cfd6..e509cfa 100644
--- a/Subscriber/PaginateElasticaQuerySubscriber.php
+++ b/Subscriber/PaginateElasticaQuerySubscriber.php
@@ -32,6 +32,10 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
if (null != $facets) {
$event->setCustomPaginationParameter('facets', $facets);
}
+ $aggregations = $results->getAggregations();
+ if (null != $aggregations) {
+ $event->setCustomPaginationParameter('aggregations', $aggregations);
+ }
$event->stopPropagation();
}
@@ -73,4 +77,4 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
'knp_pager.items' => array('items', 1)
);
}
-}
\ No newline at end of file
+}
diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php
index 7165052..30e0f7a 100644
--- a/Tests/DependencyInjection/ConfigurationTest.php
+++ b/Tests/DependencyInjection/ConfigurationTest.php
@@ -199,6 +199,24 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
$this->assertCount(3, $configuration['indexes']['test']['types']['test']['properties']);
}
+ public function testUnconfiguredType()
+ {
+ $configuration = $this->getConfigs(array(
+ 'clients' => array(
+ 'default' => array('url' => 'http://localhost:9200'),
+ ),
+ 'indexes' => array(
+ 'test' => array(
+ 'types' => array(
+ 'test' => null
+ )
+ )
+ )
+ ));
+
+ $this->assertArrayHasKey('properties', $configuration['indexes']['test']['types']['test']);
+ }
+
public function testNestedProperties()
{
$this->getConfigs(array(
diff --git a/Tests/DependencyInjection/FOSElasticaExtensionTest.php b/Tests/DependencyInjection/FOSElasticaExtensionTest.php
new file mode 100644
index 0000000..feb520f
--- /dev/null
+++ b/Tests/DependencyInjection/FOSElasticaExtensionTest.php
@@ -0,0 +1,32 @@
+setParameter('kernel.debug', true);
+
+ $extension = new FOSElasticaExtension;
+
+ $extension->load($config, $containerBuilder);
+
+ $this->assertTrue($containerBuilder->hasDefinition('fos_elastica.object_persister.test_index.child_field'));
+
+ $persisterCallDefinition = $containerBuilder->getDefinition('fos_elastica.object_persister.test_index.child_field');
+
+ $arguments = $persisterCallDefinition->getArguments();
+ $arguments = $arguments['index_3'];
+
+ $this->assertArrayHasKey('_parent', $arguments);
+ $this->assertEquals('parent_field', $arguments['_parent']['type']);
+ }
+}
diff --git a/Tests/DependencyInjection/fixtures/config.yml b/Tests/DependencyInjection/fixtures/config.yml
new file mode 100644
index 0000000..5528d18
--- /dev/null
+++ b/Tests/DependencyInjection/fixtures/config.yml
@@ -0,0 +1,21 @@
+fos_elastica:
+ clients:
+ default:
+ url: http://localhost:9200
+ indexes:
+ test_index:
+ client: default
+ types:
+ parent_field:
+ mappings:
+ text: ~
+ persistence:
+ driver: orm
+ model: foo_model
+ child_field:
+ mappings:
+ text: ~
+ persistence:
+ driver: orm
+ model: foo_model
+ _parent: { type: "parent_field", property: "parent" }
diff --git a/Tests/Doctrine/AbstractListenerTest.php b/Tests/Doctrine/AbstractListenerTest.php
index 7242255..6415053 100644
--- a/Tests/Doctrine/AbstractListenerTest.php
+++ b/Tests/Doctrine/AbstractListenerTest.php
@@ -16,7 +16,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
@@ -35,7 +35,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, false);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
@@ -55,7 +55,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
@@ -89,7 +89,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
@@ -124,7 +124,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->getId(), current($listener->scheduledForDeletion));
@@ -157,7 +157,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->identifier, current($listener->scheduledForDeletion));
diff --git a/Tests/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php
index 0c3ac17..07ff84e 100644
--- a/Tests/Doctrine/AbstractProviderTest.php
+++ b/Tests/Doctrine/AbstractProviderTest.php
@@ -173,6 +173,32 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$provider->populate();
}
+ public function testPopulateShouldClearObjectManagerForFilteredBatch()
+ {
+ $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->indexable->expects($this->any())
+ ->method('isObjectIndexable')
+ ->with('index', 'type', $this->anything())
+ ->will($this->returnValue(false));
+
+ $this->objectManager->expects($this->once())
+ ->method('clear');
+
+ $provider->populate();
+ }
+
public function testPopulateInvokesLoggerClosure()
{
$nbObjects = 1;
diff --git a/Tests/Functional/ClientTest.php b/Tests/Functional/ClientTest.php
new file mode 100644
index 0000000..8a6357a
--- /dev/null
+++ b/Tests/Functional/ClientTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Functional;
+
+use Symfony\Bundle\FrameworkBundle\Client;
+
+/**
+ * @group functional
+ */
+class ClientTest extends WebTestCase
+{
+ public function testContainerSource()
+ {
+ $client = $this->createClient(array('test_case' => 'Basic'));
+
+ $es = $client->getContainer()->get('fos_elastica.client.default');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\RoundRobin', $es->getConnectionStrategy());
+
+ $es = $client->getContainer()->get('fos_elastica.client.second_server');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\RoundRobin', $es->getConnectionStrategy());
+
+ $es = $client->getContainer()->get('fos_elastica.client.third');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\Simple', $es->getConnectionStrategy());
+ }
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->deleteTmpDir('Basic');
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->deleteTmpDir('Basic');
+ }
+}
diff --git a/Tests/Functional/MappingToElasticaTest.php b/Tests/Functional/MappingToElasticaTest.php
index f42df61..197dcca 100644
--- a/Tests/Functional/MappingToElasticaTest.php
+++ b/Tests/Functional/MappingToElasticaTest.php
@@ -32,6 +32,10 @@ class MappingToElasticaTest extends WebTestCase
$this->assertTrue($mapping['type']['properties']['field1']['store']);
$this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']);
+ $type = $this->getType($client, 'type');
+ $mapping = $type->getMapping();
+ $this->assertEquals('parent', $mapping['type']['_parent']['type']);
+
$parent = $this->getType($client, 'parent');
$mapping = $parent->getMapping();
@@ -49,6 +53,9 @@ class MappingToElasticaTest extends WebTestCase
$mapping = $type->getMapping();
$this->assertNotEmpty($mapping, 'Mapping was populated');
+ $this->assertFalse($mapping['type']['date_detection']);
+ $this->assertTrue($mapping['type']['numeric_detection']);
+ $this->assertEquals(array('yyyy-MM-dd'), $mapping['type']['dynamic_date_formats']);
$this->assertArrayHasKey('store', $mapping['type']['properties']['field1']);
$this->assertTrue($mapping['type']['properties']['field1']['store']);
$this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']);
@@ -105,6 +112,7 @@ class MappingToElasticaTest extends WebTestCase
/**
* @param Client $client
+ * @param string $type
* @return \Elastica\Type
*/
private function getType(Client $client, $type = 'type')
diff --git a/Tests/Functional/PropertyPathTest.php b/Tests/Functional/PropertyPathTest.php
new file mode 100644
index 0000000..860cb86
--- /dev/null
+++ b/Tests/Functional/PropertyPathTest.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Functional;
+
+use Elastica\Query\Match;
+
+/**
+ * @group functional
+ */
+class PropertyPathTest extends WebTestCase
+{
+ public function testContainerSource()
+ {
+ $client = $this->createClient(array('test_case' => 'ORM'));
+ /** @var \FOS\ElasticaBundle\Persister\ObjectPersister $persister */
+ $persister = $client->getContainer()->get('fos_elastica.object_persister.index.property_paths_type');
+ $obj = new TypeObj();
+ $obj->coll = 'Hello';
+ $persister->insertOne($obj);
+
+ /** @var \Elastica\Index $elClient */
+ $index = $client->getContainer()->get('fos_elastica.index.index');
+ $index->flush(true);
+
+ $query = new Match();
+ $query->setField('something', 'Hello');
+ $search = $index->createSearch($query);
+
+ $this->assertEquals(1, $search->count());
+ }
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->deleteTmpDir('Basic');
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->deleteTmpDir('Basic');
+ }
+}
diff --git a/Tests/Functional/TypeObj.php b/Tests/Functional/TypeObj.php
index 46e5968..39e9fe9 100644
--- a/Tests/Functional/TypeObj.php
+++ b/Tests/Functional/TypeObj.php
@@ -13,8 +13,10 @@ namespace FOS\ElasticaBundle\Tests\Functional;
class TypeObj
{
+ public $id = 5;
public $coll;
public $field1;
+ public $field2;
public function isIndexable()
{
diff --git a/Tests/Functional/app/Basic/config.yml b/Tests/Functional/app/Basic/config.yml
index 3c3d369..8ed88eb 100644
--- a/Tests/Functional/app/Basic/config.yml
+++ b/Tests/Functional/app/Basic/config.yml
@@ -15,7 +15,12 @@ fos_elastica:
- url: http://localhost:9200
- host: localhost
port: 9200
+ connectionStrategy: RoundRobin
second_server:
+ connections:
+ - url: http://localhost:9200
+ connection_strategy: RoundRobin
+ third:
url: http://localhost:9200
indexes:
index:
@@ -46,6 +51,8 @@ fos_elastica:
index_analyzer: my_analyzer
type:
search_analyzer: my_analyzer
+ date_detection: false
+ dynamic_date_formats: [ 'yyyy-MM-dd' ]
dynamic_templates:
- dates:
match: "date_*"
@@ -56,6 +63,7 @@ fos_elastica:
mapping:
analyzer: english
type: string
+ numeric_detection: true
properties:
field1: ~
field2:
@@ -71,6 +79,11 @@ fos_elastica:
properties:
date: { boost: 5 }
content: ~
+ multiple:
+ type: "multi_field"
+ properties:
+ name: ~
+ position: ~
user:
type: "object"
approver:
@@ -87,3 +100,4 @@ fos_elastica:
identifier: "id"
null_mappings:
mappings: ~
+ empty_index: ~
diff --git a/Tests/Functional/app/ORM/config.yml b/Tests/Functional/app/ORM/config.yml
index 02d7a92..d2ff931 100644
--- a/Tests/Functional/app/ORM/config.yml
+++ b/Tests/Functional/app/ORM/config.yml
@@ -35,6 +35,8 @@ fos_elastica:
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener:
is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()'
+ provider:
+ debug_logging: true
type2:
properties:
field1: ~
@@ -63,6 +65,18 @@ fos_elastica:
provider: ~
listener:
is_indexable_callback: [ 'FOS\ElasticaBundle\Tests\Functional\app\ORM\IndexableService', 'isntIndexable' ]
+ property_paths_type:
+ persistence:
+ driver: orm
+ model: FOS\ElasticaBundle\Tests\Functional\TypeObj
+ provider: ~
+ properties:
+ field1:
+ property_path: field2
+ something:
+ property_path: coll
+ dynamic:
+ property_path: false
second_index:
index_name: foselastica_orm_test_second_%kernel.environment%
types:
diff --git a/Tests/Functional/app/Serializer/config.yml b/Tests/Functional/app/Serializer/config.yml
index 9bea4ba..de7caec 100644
--- a/Tests/Functional/app/Serializer/config.yml
+++ b/Tests/Functional/app/Serializer/config.yml
@@ -28,7 +28,7 @@ fos_elastica:
serializer: ~
indexes:
index:
- index_name: foselastica_test_%kernel.environment%
+ index_name: foselastica_ser_test_%kernel.environment%
types:
type:
properties:
diff --git a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
index 1fa6a8e..1dbf5fd 100644
--- a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
+++ b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
@@ -2,6 +2,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer;
+use FOS\ElasticaBundle\Event\TransformEvent;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
@@ -132,6 +133,35 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
}
}
+ public function testTransformerDispatches()
+ {
+ $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')
+ ->getMock();
+ $dispatcher->expects($this->once())
+ ->method('dispatch')
+ ->with(
+ TransformEvent::POST_TRANSFORM,
+ $this->isInstanceOf('FOS\ElasticaBundle\Event\TransformEvent')
+ );
+
+ $transformer = $this->getTransformer($dispatcher);
+ $transformer->transform(new POPO(), array());
+ }
+
+ public function testPropertyPath()
+ {
+ $transformer = $this->getTransformer();
+
+ $document = $transformer->transform(new POPO(), array('name' => array('property_path' => false)));
+ $this->assertInstanceOf('Elastica\Document', $document);
+ $this->assertFalse($document->has('name'));
+
+ $document = $transformer->transform(new POPO(), array('realName' => array('property_path' => 'name')));
+ $this->assertInstanceOf('Elastica\Document', $document);
+ $this->assertTrue($document->has('realName'));
+ $this->assertEquals('someName', $document->get('realName'));
+ }
+
public function testThatCanTransformObject()
{
$transformer = $this->getTransformer();
@@ -250,7 +280,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$document = $transformer->transform(new POPO(), array(
'sub' => array(
'type' => 'nested',
- 'properties' => array('foo' => '~')
+ 'properties' => array('foo' => array())
)
));
$data = $document->getData();
@@ -295,8 +325,8 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(array_key_exists('obj', $data));
$this->assertInternalType('array', $data['obj']);
$this->assertEquals(array(
- 'foo' => 'foo',
- 'bar' => 'foo',
+ 'foo' => 'foo',
+ 'bar' => 'foo',
'id' => 1
), $data['obj']);
}
@@ -387,11 +417,12 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
}
/**
+ * @param null|\Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* @return ModelToElasticaAutoTransformer
*/
- private function getTransformer()
+ private function getTransformer($dispatcher = null)
{
- $transformer = new ModelToElasticaAutoTransformer();
+ $transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher);
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
return $transformer;
diff --git a/Transformer/ModelToElasticaAutoTransformer.php b/Transformer/ModelToElasticaAutoTransformer.php
index 106db15..6a9fbca 100644
--- a/Transformer/ModelToElasticaAutoTransformer.php
+++ b/Transformer/ModelToElasticaAutoTransformer.php
@@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Transformer;
+use FOS\ElasticaBundle\Event\TransformEvent;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Elastica\Document;
@@ -12,6 +14,11 @@ use Elastica\Document;
*/
class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterface
{
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $dispatcher;
+
/**
* Optional parameters
*
@@ -32,10 +39,12 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
* Instanciates a new Mapper
*
* @param array $options
+ * @param EventDispatcherInterface $dispatcher
*/
- public function __construct(array $options = array())
+ public function __construct(array $options = array(), EventDispatcherInterface $dispatcher = null)
{
$this->options = array_merge($this->options, $options);
+ $this->dispatcher = $dispatcher;
}
/**
@@ -66,16 +75,24 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
$property = (null !== $mapping['property'])?$mapping['property']:$mapping['type'];
$value = $this->propertyAccessor->getValue($object, $property);
$document->setParent($this->propertyAccessor->getValue($value, $mapping['identifier']));
+
continue;
}
- $value = $this->propertyAccessor->getValue($object, $key);
+ $path = isset($mapping['property_path']) ?
+ $mapping['property_path'] :
+ $key;
+ if (false === $path) {
+ continue;
+ }
+ $value = $this->propertyAccessor->getValue($object, $path);
if (isset($mapping['type']) && in_array($mapping['type'], array('nested', 'object')) && isset($mapping['properties']) && !empty($mapping['properties'])) {
/* $value is a nested document or object. Transform $value into
* an array of documents, respective the mapped properties.
*/
$document->set($key, $this->transformNested($value, $mapping['properties']));
+
continue;
}
@@ -86,12 +103,20 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
} else {
$document->addFileContent($key, $value);
}
+
continue;
}
$document->set($key, $this->normalizeValue($value));
}
+ if ($this->dispatcher) {
+ $event = new TransformEvent($document, $fields, $object);
+ $this->dispatcher->dispatch(TransformEvent::POST_TRANSFORM, $event);
+
+ $document = $event->getDocument();
+ }
+
return $document;
}
diff --git a/composer.json b/composer.json
index 833621a..917e839 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,7 @@
"symfony/console": "~2.1",
"symfony/form": "~2.1",
"symfony/property-access": "~2.2",
- "ruflin/elastica": ">=0.90.10.0, <1.4-dev",
+ "ruflin/elastica": ">=0.90.10.0, <1.5-dev",
"psr/log": "~1.0"
},
"require-dev":{