Merge branch 'master' into pr/744

Conflicts:
	Command/PopulateCommand.php
This commit is contained in:
Tim Nagel 2015-03-11 14:11:18 +11:00
commit f6df88cc67
41 changed files with 822 additions and 182 deletions

View file

@ -1,5 +1,9 @@
language: php
cache:
directories:
- $HOME/.composer/cache
php:
- 5.3
- 5.4
@ -21,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

View file

@ -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

View file

@ -14,6 +14,20 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
* 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.
* Removed `Doctrine\Listener#getSubscribedEvents`. The container
* 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.
this class on every request where doctrine is active. #729
* 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

View file

@ -2,22 +2,30 @@
namespace FOS\ElasticaBundle\Command;
use FOS\ElasticaBundle\Event\IndexPopulateEvent;
use FOS\ElasticaBundle\Event\PopulateEvent;
use FOS\ElasticaBundle\Event\TypePopulateEvent;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use FOS\ElasticaBundle\Index\IndexManager;
use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Provider\ProviderRegistry;
use FOS\ElasticaBundle\Resetter;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Helper\ProgressBar;
/**
* Populate the search index
*/
class PopulateCommand extends ContainerAwareCommand
{
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $dispatcher;
/**
* @var IndexManager
*/
@ -29,9 +37,9 @@ class PopulateCommand extends ContainerAwareCommand
private $providerRegistry;
/**
* @var EventDispatcherInterface
* @var Resetter
*/
private $eventDispatcher;
private $resetter;
/**
* @see Symfony\Component\Console\Command\Command::configure()
@ -56,9 +64,10 @@ class PopulateCommand extends ContainerAwareCommand
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->dispatcher = $this->getContainer()->get('event_dispatcher');
$this->indexManager = $this->getContainer()->get('fos_elastica.index_manager');
$this->providerRegistry = $this->getContainer()->get('fos_elastica.provider_registry');
$this->eventDispatcher = $this->getContainer()->get('event_dispatcher');
$this->resetter = $this->getContainer()->get('fos_elastica.resetter');
}
/**
@ -100,6 +109,86 @@ class PopulateCommand extends ContainerAwareCommand
}
}
/**
* @param ProviderInterface $provider
* @param OutputInterface $output
* @param string $index
* @param string $type
* @param array $options
*/
private function doPopulateType(ProviderInterface $provider, OutputInterface $output, $index, $type, $options)
{
$event = new TypePopulateEvent($index, $type, $options);
$this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event);
$loggerClosure = $this->getLoggerClosure($output, $index, $type);
$provider->populate($loggerClosure, $event->getOptions());
$this->dispatcher->dispatch(TypePopulateEvent::POST_TYPE_POPULATE, $event);
}
/**
* 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.
*
@ -110,13 +199,23 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndex(OutputInterface $output, $index, $reset, $options)
{
/** @var $providers ProviderInterface[] */
$event = new IndexPopulateEvent($index, $reset, $options);
$this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event);
if ($event->isReset()) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
$this->resetter->resetIndex($index, true);
}
$providers = $this->providerRegistry->getIndexProviders($index);
$this->populate($output, $providers, $index, null, $reset, $options);
foreach ($providers as $type => $provider) {
$this->doPopulateType($provider, $output, $index, $type, $event->getOptions());
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->indexManager->getIndex($index)->refresh();
$this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);
$this->refreshIndex($output, $index);
}
/**
@ -130,48 +229,31 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options)
{
$provider = $this->providerRegistry->getProvider($index, $type);
if ($reset) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
$this->resetter->resetIndexType($index, $type);
}
$this->populate($output, array($type => $provider), $index, $type, $reset, $options);
$provider = $this->providerRegistry->getProvider($index, $type);
$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();
}
/**
* @param OutputInterface $output
* @param ProviderInterface[] $providers
* @param string $index
* @param string $type
* @param boolean $reset
* @param array $options
*/
private function populate(OutputInterface $output, array $providers, $index, $type, $reset, $options)
{
if ($reset) {
if ($type) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
} else {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
}
}
$this->eventDispatcher->dispatch(PopulateEvent::PRE_INDEX_POPULATE, new PopulateEvent($index, $type, $reset, $options));
foreach ($providers as $providerType => $provider) {
$event = new PopulateEvent($index, $providerType, $reset, $options);
$this->eventDispatcher->dispatch(PopulateEvent::PRE_TYPE_POPULATE, $event);
$loggerClosure = function($message) use ($output, $index, $providerType) {
$output->writeln(sprintf('<info>Populating</info> %s/%s, %s', $index, $providerType, $message));
};
$provider->populate($loggerClosure, $options);
$this->eventDispatcher->dispatch(PopulateEvent::POST_TYPE_POPULATE, $event);
}
$this->eventDispatcher->dispatch(PopulateEvent::POST_INDEX_POPULATE, new PopulateEvent($index, $type, $reset, $options));
}
}

View file

@ -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;
}
}

View file

@ -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
*/

View file

@ -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()
@ -199,7 +210,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 +246,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())

View file

@ -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);

View file

@ -39,7 +39,7 @@ abstract class AbstractProvider extends BaseAbstractProvider
}
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
* {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
@ -56,34 +56,19 @@ abstract class AbstractProvider extends BaseAbstractProvider
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
if ($loggerClosure) {
$stepNbObjects = count($objects);
}
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
if (!$objects) {
if ($loggerClosure) {
$loggerClosure('<info>Entire batch was filtered away, skipping...</info>');
}
if ($this->options['clear_object_manager']) {
$manager->clear();
}
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(sprintf('<error>%s</error>',$e->getMessage()));
}
}
}
}
@ -95,11 +80,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);
}
}

View file

@ -17,24 +17,16 @@ use Symfony\Component\EventDispatcher\Event;
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
class PopulateEvent extends Event
class IndexPopulateEvent extends Event
{
const PRE_INDEX_POPULATE = 'elastica.index.index_pre_populate';
const POST_INDEX_POPULATE = 'elastica.index.index_post_populate';
const PRE_TYPE_POPULATE = 'elastica.index.type_pre_populate';
const POST_TYPE_POPULATE = 'elastica.index.type_post_populate';
/**
* @var string
*/
private $index;
/**
* @var string
*/
private $type;
/**
* @var bool
*/
@ -47,14 +39,12 @@ class PopulateEvent extends Event
/**
* @param string $index
* @param string|null $type
* @param boolean $reset
* @param array $options
*/
public function __construct($index, $type, $reset, $options)
public function __construct($index, $reset, $options)
{
$this->index = $index;
$this->type = $type;
$this->reset = $reset;
$this->options = $options;
}
@ -67,14 +57,6 @@ class PopulateEvent extends Event
return $this->index;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return boolean
*/

78
Event/TransformEvent.php Normal file
View 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;
}
}

View file

@ -0,0 +1,80 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Populate Event
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
class TypePopulateEvent extends Event
{
const PRE_TYPE_POPULATE = 'elastica.index.type_pre_populate';
const POST_TYPE_POPULATE = 'elastica.index.type_post_populate';
/**
* @var string
*/
private $index;
/**
* @var string
*/
private $type;
/**
* @var array
*/
private $options;
/**
* @param string $index
* @param string $type
* @param array $options
*/
public function __construct($index, $type, $options)
{
$this->index = $index;
$this->type = $type;
$this->options = $options;
}
/**
* @return string
*/
public function getIndex()
{
return $this->index;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
public function setOptions($options)
{
$this->options = $options;
}
}

View file

@ -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']);
}

View file

@ -62,6 +62,9 @@ class Resetter
/**
* Deletes and recreates all indexes
*
* @param bool $populating
* @param bool $force
*/
public function resetAllIndexes($populating = false, $force = false)
{

View file

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

View file

@ -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()

View file

@ -58,7 +58,7 @@ class ProviderRegistry implements ContainerAwareInterface
* Providers will be indexed by "type" strings in the returned array.
*
* @param string $index
* @return array of ProviderInterface instances
* @return ProviderInterface[]
* @throws \InvalidArgumentException if no providers were registered for the index
*/
public function getIndexProviders($index)

View file

@ -23,7 +23,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 -->

View file

@ -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>

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

View file

@ -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:

View file

@ -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

View file

@ -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
{

View file

@ -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)

View file

@ -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
$ 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)

View file

@ -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
-----------------------------------------------

View file

@ -160,7 +160,7 @@ fos_elastica:
site:
settings:
index:
analysis:
analysis:
analyzer:
my_analyzer:
type: snowball

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -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>

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

View 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" }

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

View file

@ -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')

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

View file

@ -13,8 +13,10 @@ namespace FOS\ElasticaBundle\Tests\Functional;
class TypeObj
{
public $id = 5;
public $coll;
public $field1;
public $field2;
public function isIndexable()
{

View file

@ -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: ~

View file

@ -65,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:

View file

@ -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:

View file

@ -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;

View file

@ -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;
}

View file

@ -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":{