Merge branch 'master' into pr/725
Conflicts: Doctrine/AbstractProvider.php
This commit is contained in:
commit
3d69c08b5a
10
.travis.yml
10
.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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
'<info>Populating</info> <comment>%s/%s</comment> %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('<info>Populating</info> <comment>%s/%s</comment>', $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('<info>Populating</info> %s/%s, %s', $index, $type, $message));
|
||||
};
|
||||
|
||||
$provider->populate($loggerClosure, $options);
|
||||
$this->doPopulateType($provider, $output, $index, $type, $options);
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $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('<info>Populating</info> %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('<info>Refreshing</info> <comment>%s</comment>', $index));
|
||||
$this->indexManager->getIndex($index)->refresh();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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('<info>Entire batch was filtered away, skipping...</info>');
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$ignoreErrors) {
|
||||
$this->objectPersister->insertMany($objects);
|
||||
} else {
|
||||
try {
|
||||
if ($objects) {
|
||||
if (!$ignoreErrors) {
|
||||
$this->objectPersister->insertMany($objects);
|
||||
} catch(BulkResponseException $e) {
|
||||
if ($loggerClosure) {
|
||||
$loggerClosure(sprintf('<error>%s</error>',$e->getMessage()));
|
||||
} else {
|
||||
try {
|
||||
$this->objectPersister->insertMany($objects);
|
||||
} catch(BulkResponseException $e) {
|
||||
if ($loggerClosure) {
|
||||
$loggerClosure($batchSize, $nbObjects, sprintf('<error>%s</error>',$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.
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
78
Event/TransformEvent.php
Normal file
78
Event/TransformEvent.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the FOSElasticaBundle project.
|
||||
*
|
||||
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace FOS\ElasticaBundle\Event;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
class 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;
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ class Resetter
|
|||
|
||||
/**
|
||||
* Deletes and recreates all indexes
|
||||
*
|
||||
* @param bool $populating
|
||||
* @param bool $force
|
||||
*/
|
||||
public function resetAllIndexes($populating = false, $force = false)
|
||||
{
|
||||
|
|
|
@ -40,6 +40,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.
|
||||
*
|
||||
|
|
|
@ -31,4 +31,11 @@ interface PaginatorAdapterInterface
|
|||
* @return mixed
|
||||
*/
|
||||
function getFacets();
|
||||
|
||||
/**
|
||||
* Returns Aggregations
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function getAggregations();
|
||||
}
|
||||
|
|
|
@ -28,4 +28,11 @@ interface PartialResultsInterface
|
|||
* @return array
|
||||
*/
|
||||
function getFacets();
|
||||
|
||||
/**
|
||||
* Returns the aggregations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function getAggregations();
|
||||
}
|
|
@ -37,6 +37,11 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
|
|||
*/
|
||||
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;
|
||||
}
|
||||
|
@ -127,6 +136,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
|
||||
*
|
||||
|
|
|
@ -49,4 +49,16 @@ class RawPartialResults implements PartialResultsInterface
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getAggregations()
|
||||
{
|
||||
if ($this->resultSet->hasAggregations()) {
|
||||
return $this->resultSet->getAggregations();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -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('<info>Entire batch was filtered away, skipping...</info>');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
<service id="fos_elastica.listener.prototype.mongodb" class="%fos_elastica.listener.prototype.mongodb.class%" public="false" abstract="true">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument type="collection" /> <!-- events -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument type="collection" /> <!-- configuration -->
|
||||
<argument /> <!-- logger -->
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
<service id="fos_elastica.listener.prototype.orm" class="%fos_elastica.listener.prototype.orm.class%" public="false" abstract="true">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument type="collection" /> <!-- events -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument type="collection" /> <!-- configuration -->
|
||||
<argument on-invalid="ignore" /> <!-- logger -->
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
|
||||
<services>
|
||||
<service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true">
|
||||
<argument type="collection" /> <!-- options -->
|
||||
<argument type="collection" /> <!-- options -->
|
||||
<argument type="service" id="event_dispatcher" /> <!-- options -->
|
||||
<call method="setPropertyAccessor">
|
||||
<argument type="service" id="fos_elastica.property_accessor" />
|
||||
</call>
|
||||
|
|
33
Resources/doc/cookbook/custom-properties.md
Normal file
33
Resources/doc/cookbook/custom-properties.md
Normal file
|
@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
|
@ -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
|
||||
<?php
|
||||
|
||||
namespace Acme\ElasticaBundle\SearchRepository;
|
||||
|
@ -23,37 +23,41 @@ class UserRepository extends Repository
|
|||
|
||||
To use the custom repository specify it in the mapping for the entity:
|
||||
|
||||
fos_elastica:
|
||||
clients:
|
||||
default: { host: localhost, port: 9200 }
|
||||
indexes:
|
||||
website:
|
||||
client: default
|
||||
types:
|
||||
user:
|
||||
mappings:
|
||||
# your mappings
|
||||
persistence:
|
||||
driver: orm
|
||||
model: Application\UserBundle\Entity\User
|
||||
provider: ~
|
||||
finder: ~
|
||||
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
|
||||
```yaml
|
||||
fos_elastica:
|
||||
clients:
|
||||
default: { host: localhost, port: 9200 }
|
||||
indexes:
|
||||
website:
|
||||
client: default
|
||||
types:
|
||||
user:
|
||||
mappings:
|
||||
# your mappings
|
||||
persistence:
|
||||
driver: orm
|
||||
model: Application\UserBundle\Entity\User
|
||||
provider: ~
|
||||
finder: ~
|
||||
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
|
||||
```
|
||||
|
||||
Then the custom queries will be available when using the repository returned from the manager:
|
||||
|
||||
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
|
||||
$repositoryManager = $container->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
|
||||
<?php
|
||||
|
||||
namespace Application\UserBundle\Entity;
|
||||
|
|
|
@ -8,7 +8,7 @@ index and type for which the service will provide.
|
|||
# app/config/config.yml
|
||||
services:
|
||||
acme.search_provider.user:
|
||||
class: Acme\UserBundle\Search\UserProvider
|
||||
class: Acme\UserBundle\Provider\UserProvider
|
||||
arguments:
|
||||
- @fos_elastica.index.website.user
|
||||
tags:
|
||||
|
|
|
@ -11,6 +11,11 @@ fos_elastica:
|
|||
connections:
|
||||
- url: http://es1.example.net:9200
|
||||
- url: http://es2.example.net:9200
|
||||
connection_strategy: RoundRobin
|
||||
```
|
||||
|
||||
Elastica allows for definition of different connection strategies and by default
|
||||
supports `RoundRobin` and `Simple`. You can see definitions for these strategies
|
||||
in the `Elastica\Connection\Strategy` namespace.
|
||||
|
||||
For more information on Elastica clustering see http://elastica.io/getting-started/installation.html#section-connect-cluster
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Acme\ElasticaBundle;
|
|||
use Elastica\Exception\ExceptionInterface;
|
||||
use Elastica\Request;
|
||||
use Elastica\Response;
|
||||
use FOS\ElasticaBundle\Client as BaseClient;
|
||||
use FOS\ElasticaBundle\Elastica\Client as BaseClient;
|
||||
|
||||
class Client extends BaseClient
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ Cookbook Entries
|
|||
----------------
|
||||
|
||||
* [Aliased Indexes](cookbook/aliased-indexes.md)
|
||||
* [Custom Indexed Properties](cookbook/custom-properties.md)
|
||||
* [Custom Repositories](cookbook/custom-repositories.md)
|
||||
* [HTTP Headers for Elastica](cookbook/elastica-client-http-headers.md)
|
||||
* Performance - [Logging](cookbook/logging.md)
|
||||
|
|
|
@ -1,40 +1,50 @@
|
|||
Step 1: Setting up the bundle
|
||||
=============================
|
||||
|
||||
A) Install FOSElasticaBundle
|
||||
----------------------------
|
||||
A: Download the Bundle
|
||||
----------------------
|
||||
|
||||
FOSElasticaBundle is installed using [Composer](https://getcomposer.org).
|
||||
Open a command console, enter your project directory and execute the
|
||||
following command to download the latest stable version of this bundle:
|
||||
|
||||
```bash
|
||||
$ php composer.phar require friendsofsymfony/elastica-bundle "~3.0"
|
||||
$ composer require friendsofsymfony/elastica-bundle "~3.0"
|
||||
```
|
||||
|
||||
This command requires you to have Composer installed globally, as explained
|
||||
in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
|
||||
of the Composer documentation.
|
||||
|
||||
### Elasticsearch
|
||||
|
||||
Instructions for installing and deploying Elasticsearch may be found
|
||||
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
|
||||
Instructions for installing and deploying Elasticsearch may be found [here](http://www.elasticsearch.org/guide/reference/setup/installation/).
|
||||
|
||||
Step 2: Enable the Bundle
|
||||
-------------------------
|
||||
|
||||
B) Enable FOSElasticaBundle
|
||||
---------------------------
|
||||
|
||||
Enable FOSElasticaBundle in your AppKernel:
|
||||
Then, enable the bundle by adding the following line in the `app/AppKernel.php`
|
||||
file of your project:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// app/AppKernel.php
|
||||
|
||||
public function registerBundles()
|
||||
// ...
|
||||
class AppKernel extends Kernel
|
||||
{
|
||||
$bundles = array(
|
||||
public function registerBundles()
|
||||
{
|
||||
$bundles = array(
|
||||
// ...
|
||||
new FOS\ElasticaBundle\FOSElasticaBundle(),
|
||||
);
|
||||
|
||||
// ...
|
||||
new FOS\ElasticaBundle\FOSElasticaBundle(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
C) Basic Bundle Configuration
|
||||
C: Basic Bundle Configuration
|
||||
-----------------------------
|
||||
|
||||
The basic minimal configuration for FOSElasticaBundle is one client with one Elasticsearch
|
||||
|
@ -48,27 +58,30 @@ fos_elastica:
|
|||
clients:
|
||||
default: { host: localhost, port: 9200 }
|
||||
indexes:
|
||||
search: ~
|
||||
app: ~
|
||||
```
|
||||
|
||||
In this example, an Elastica index (an instance of `Elastica\Index`) is available as a
|
||||
service with the key `fos_elastica.index.search`.
|
||||
service with the key `fos_elastica.index.app`.
|
||||
|
||||
If the Elasticsearch index name needs to be different to the service name in your
|
||||
application, for example, renaming the search index based on different environments.
|
||||
You may want the index `app` to be named something else on ElasticSearch depending on
|
||||
if your application is running in a different env or other conditions that suit your
|
||||
application. To set your customer index to a name that depends on the environment of your
|
||||
Symfony application, use the example below:
|
||||
|
||||
```yaml
|
||||
#app/config/config.yml
|
||||
fos_elastica:
|
||||
indexes:
|
||||
search:
|
||||
index_name: search_dev
|
||||
app:
|
||||
index_name: app_%kernel.env%
|
||||
```
|
||||
|
||||
In this case, the service `fos_elastica.index.search` will be using an Elasticsearch
|
||||
index of search_dev.
|
||||
In this case, the service `fos_elastica.index.app` will relate to an ElasticSearch index
|
||||
that varies depending on your kernel's environment. For example, in dev it will relate to
|
||||
`app_dev`.
|
||||
|
||||
D) Defining index types
|
||||
D: Defining index types
|
||||
-----------------------
|
||||
|
||||
By default, FOSElasticaBundle requires each type that is to be indexed to be mapped.
|
||||
|
@ -81,7 +94,7 @@ will end up being indexed.
|
|||
```yaml
|
||||
fos_elastica:
|
||||
indexes:
|
||||
search:
|
||||
app:
|
||||
types:
|
||||
user:
|
||||
mappings:
|
||||
|
@ -92,7 +105,7 @@ fos_elastica:
|
|||
```
|
||||
|
||||
Each defined type is made available as a service, and in this case the service key is
|
||||
`fos_elastica.index.search.user` and is an instance of `Elastica\Type`.
|
||||
`fos_elastica.index.app.user` and is an instance of `Elastica\Type`.
|
||||
|
||||
FOSElasticaBundle requires a provider for each type that will notify when an object
|
||||
that maps to a type has been modified. The bundle ships with support for Doctrine and
|
||||
|
@ -122,7 +135,7 @@ Below is an example for the Doctrine ORM.
|
|||
There are a significant number of options available for types, that can be
|
||||
[found here](types.md)
|
||||
|
||||
E) Populating the Elasticsearch index
|
||||
E: Populating the Elasticsearch index
|
||||
-------------------------------------
|
||||
|
||||
When using the providers and listeners that come with the bundle, any new or modified
|
||||
|
@ -137,7 +150,7 @@ $ php app/console fos:elastica:populate
|
|||
The command will also create all indexes and types defined if they do not already exist
|
||||
on the Elasticsearch server.
|
||||
|
||||
F) Usage
|
||||
F: Usage
|
||||
--------
|
||||
|
||||
Usage documentation for the bundle is available [here](usage.md)
|
||||
|
|
|
@ -1,6 +1,34 @@
|
|||
Type configuration
|
||||
==================
|
||||
|
||||
Custom Property Paths
|
||||
---------------------
|
||||
|
||||
Since FOSElasticaBundle 3.1.0, it is now possible to define custom property paths
|
||||
to be used for data retrieval from the underlying model.
|
||||
|
||||
```yaml
|
||||
user:
|
||||
mappings:
|
||||
username:
|
||||
property_path: indexableUsername
|
||||
firstName:
|
||||
property_path: names[first]
|
||||
```
|
||||
|
||||
This feature uses the Symfony PropertyAccessor component and supports all features
|
||||
that the component supports.
|
||||
|
||||
The above example would retrieve an indexed field `username` from the property
|
||||
`User->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
|
||||
-----------------------------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1 KiB |
|
@ -23,7 +23,7 @@
|
|||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon"><img src="{{ asset('bundles/foselastica/images/elastica.png') }}" alt="" /></span>
|
||||
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAQAAADPJofWAAAAAXNSR0IArs4c6QAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2woEDg0xnxGaxwAAA51JREFUOMt100ts1FUUx/Hvufc/8x9mOrTTDpbysEVsqTzUqAsRgpqQGIJiIhtZSEJcqNGFJrSiZSPYkKALBUyAiLEuWKjBhdFAaEwwPNQqEAgEaA0FEWh4lE477/+9x0WLsST+Fmf1ycnJvecI/0knW4ENgZkhr5gVMtcEMmB64z1msCOCboR70tlkOuQdQREMBovF7tQt71/mXtzZQo8s89jLycFETiSqKd/v5wSY32VN18Ak3JGiR1d7an+Y3Z/Np7yVeCY/9czU4efF2iPxZ+wLXPgXP7WCTZ7MvvlnZlbqSJnGGbMeam5pk6HTIw9qs7tiH6D9vrbXW9e1Tus/sbhHZ8VOtfU1uZSExCUe1ExLtqayjcPnx4qNssjOs3TQrY9rYdVB2aFRw7HWq2kbJxCjFAsDoY+3hbkbw9ebNBsQ6EJVNlb3yQIDY8lcaAIsBisxiSXGToTNWqi/JUWXMIoabrK/95ymDNYHzmDUYAiwWOImOle+EI2AxyiKVydQGVAIoyneiBUNsAQEWIn+LA2NJqOYw3hUFQV2DFbu+FSuKTk9kRGMBoyPE7m8vza7UhMrmQNlV+NwAlDZXmXkkcHaeNamQBCEiLyeb7w631C71yxfr8/6YT8GUP6ifKlcf3xJX26kWNQyJfL+TvVs3S8rCw31hfRm2/KzL+iGQz8BnLjz8EVd5TPXF9wI/E0ZK/gr6SOLj7+Yz9RRu2bmMXm6y2/0A/rS0f6JL985+prVQAJiKEU8kLid6ezaA8Gh7iVP6BKtHad7njQr+xmSeFELLi2YovwlJ5Nbu07DNoLFoVNVrwBvh6feSMwSDcVuLvWF0y3Brd9mDlRlTsuVweGPCRzI+NOBW2rW3iYv5ZPfbrm7ji2fx1/Vkn7U/OGlivE4HA5Yh+yqUKFAYa3QfXcVf/R7Segy3wDGo4x3Tr3H3LLmqX6mZ5+jC4A5DO7jLR3SmAoYj8eTqx5uMR+UKErp78q2/e7ARNuLgE940bR1E9jpcKx+16MxJUd5+68XJp+a5DnqFxU/yYZG8XiSfJ+awkIW9WW+zLF8sh6NeqJb0cs+biIcKjW8u3T3xj+uZXZ/PbSe3km2nHWbogb3ZlSgPWz/bt71tsf432Qa64bSh5PTIFAU0pqdG3oRIiCgBBgsVTwRpYwmnWgMAlUd1aR+6m+DA4QqBlDKRDgifCqq8WO+CoFW9Ctd7dvBA+NV8DgiognuSv4bbsA/e9y1PQRZggAAAAAASUVORK5CYII=" alt="" /></span>
|
||||
<strong>Elastica</strong>
|
||||
<span class="count">
|
||||
<span>{{ collector.querycount }}</span>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
32
Tests/DependencyInjection/FOSElasticaExtensionTest.php
Normal file
32
Tests/DependencyInjection/FOSElasticaExtensionTest.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace FOS\ElasticaBundle\Tests\DependencyInjection;
|
||||
|
||||
use FOS\ElasticaBundle\DependencyInjection\FOSElasticaExtension;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class FOSElasticaExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testShouldAddParentParamToObjectPersisterCall()
|
||||
{
|
||||
$config = Yaml::parse(file_get_contents(__DIR__.'/fixtures/config.yml'));
|
||||
|
||||
$containerBuilder = new ContainerBuilder;
|
||||
$containerBuilder->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']);
|
||||
}
|
||||
}
|
21
Tests/DependencyInjection/fixtures/config.yml
Normal file
21
Tests/DependencyInjection/fixtures/config.yml
Normal file
|
@ -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" }
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
48
Tests/Functional/ClientTest.php
Normal file
48
Tests/Functional/ClientTest.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the FOSElasticaBundle project.
|
||||
*
|
||||
* (c) Tim Nagel <tim@nagel.com.au>
|
||||
*
|
||||
* 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');
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
|
|
54
Tests/Functional/PropertyPathTest.php
Normal file
54
Tests/Functional/PropertyPathTest.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the FOSElasticaBundle project.
|
||||
*
|
||||
* (c) Tim Nagel <tim@nagel.com.au>
|
||||
*
|
||||
* 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');
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@ namespace FOS\ElasticaBundle\Tests\Functional;
|
|||
|
||||
class TypeObj
|
||||
{
|
||||
public $id = 5;
|
||||
public $coll;
|
||||
public $field1;
|
||||
public $field2;
|
||||
|
||||
public function isIndexable()
|
||||
{
|
||||
|
|
|
@ -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: ~
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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":{
|
||||
|
|
Loading…
Reference in a new issue