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 language: php
cache:
directories:
- $HOME/.composer/cache
php: php:
- 5.3 - 5.3
- 5.4 - 5.4
@ -21,7 +25,8 @@ before_script:
- sh -c 'if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi;' - sh -c 'if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi;'
- composer install --dev --prefer-source - composer install --dev --prefer-source
script: vendor/bin/phpunit --coverage-clover=coverage.clover script:
- vendor/bin/phpunit --coverage-clover=coverage.clover
services: services:
- elasticsearch - 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 To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x` `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 * 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called * 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: `Doctrine\Listener#scheduleForDeletion` access changed to private.
* BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that * BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
returns a boolean value if it will handle a given object or not. 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 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; namespace FOS\ElasticaBundle\Command;
use FOS\ElasticaBundle\Event\IndexPopulateEvent;
use FOS\ElasticaBundle\Event\PopulateEvent; use FOS\ElasticaBundle\Event\PopulateEvent;
use FOS\ElasticaBundle\Event\TypePopulateEvent;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use FOS\ElasticaBundle\Index\IndexManager; use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Provider\ProviderRegistry; use FOS\ElasticaBundle\Provider\ProviderRegistry;
use FOS\ElasticaBundle\Resetter;
use FOS\ElasticaBundle\Provider\ProviderInterface; use FOS\ElasticaBundle\Provider\ProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Console\Helper\ProgressBar;
/** /**
* Populate the search index * Populate the search index
*/ */
class PopulateCommand extends ContainerAwareCommand class PopulateCommand extends ContainerAwareCommand
{ {
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $dispatcher;
/** /**
* @var IndexManager * @var IndexManager
*/ */
@ -29,9 +37,9 @@ class PopulateCommand extends ContainerAwareCommand
private $providerRegistry; private $providerRegistry;
/** /**
* @var EventDispatcherInterface * @var Resetter
*/ */
private $eventDispatcher; private $resetter;
/** /**
* @see Symfony\Component\Console\Command\Command::configure() * @see Symfony\Component\Console\Command\Command::configure()
@ -56,9 +64,10 @@ class PopulateCommand extends ContainerAwareCommand
*/ */
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
{ {
$this->dispatcher = $this->getContainer()->get('event_dispatcher');
$this->indexManager = $this->getContainer()->get('fos_elastica.index_manager'); $this->indexManager = $this->getContainer()->get('fos_elastica.index_manager');
$this->providerRegistry = $this->getContainer()->get('fos_elastica.provider_registry'); $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. * 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) 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); $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->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);
$this->indexManager->getIndex($index)->refresh();
$this->refreshIndex($output, $index);
} }
/** /**
@ -130,48 +229,31 @@ class PopulateCommand extends ContainerAwareCommand
*/ */
private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options) 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)); $output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->indexManager->getIndex($index)->refresh(); $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(); $indexes = array();
foreach ($this->configArray as $config) { foreach ($this->configArray as $config) {
$types = array(); $types = $this->getTypes($config);
foreach ($config['types'] as $typeConfig) {
$types[$typeConfig['name']] = new TypeConfig(
$typeConfig['name'],
$typeConfig['mapping'],
$typeConfig['config']
);
// TODO: handle prototypes..
}
$index = new IndexConfig($config['name'], $types, array( $index = new IndexConfig($config['name'], $types, array(
'elasticSearchName' => $config['elasticsearch_name'], 'elasticSearchName' => $config['elasticsearch_name'],
'settings' => $config['settings'], 'settings' => $config['settings'],
@ -61,4 +52,28 @@ class ContainerSource implements SourceInterface
return $indexes; 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; $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 * @return string|null
*/ */
@ -61,6 +77,14 @@ class TypeConfig
null; null;
} }
/**
* @return bool|null
*/
public function getNumericDetection()
{
return $this->getConfig('numeric_detection');
}
/** /**
* @return string * @return string
*/ */

View file

@ -84,6 +84,16 @@ class Configuration implements ConfigurationInterface
return $v; return $v;
}) })
->end() ->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. // If there is no connections array key defined, assume a single connection.
->beforeNormalization() ->beforeNormalization()
->ifTrue(function ($v) { return is_array($v) && !array_key_exists('connections', $v); }) ->ifTrue(function ($v) { return is_array($v) && !array_key_exists('connections', $v); })
@ -124,6 +134,7 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->scalarNode('timeout')->end() ->scalarNode('timeout')->end()
->scalarNode('headers')->end() ->scalarNode('headers')->end()
->scalarNode('connectionStrategy')->defaultValue('Simple')->end()
->end() ->end()
->end() ->end()
->end() ->end()
@ -199,7 +210,17 @@ class Configuration implements ConfigurationInterface
isset($v['persistence']['listener']['is_indexable_callback']); isset($v['persistence']['listener']['is_indexable_callback']);
}) })
->then(function ($v) { ->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']); unset($v['persistence']['listener']['is_indexable_callback']);
return $v; return $v;
@ -225,7 +246,10 @@ class Configuration implements ConfigurationInterface
}) })
->end() ->end()
->children() ->children()
->booleanNode('date_detection')->end()
->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end()
->scalarNode('index_analyzer')->end() ->scalarNode('index_analyzer')->end()
->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end() ->scalarNode('search_analyzer')->end()
->variableNode('indexable_callback')->end() ->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode()) ->append($this->getPersistenceNode())

View file

@ -241,6 +241,9 @@ class FOSElasticaExtension extends Extension
'serializer', 'serializer',
'index_analyzer', 'index_analyzer',
'search_analyzer', 'search_analyzer',
'date_detection',
'dynamic_date_formats',
'numeric_detection',
) as $field) { ) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ? $typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] : $type[$field] :
@ -393,7 +396,12 @@ class FOSElasticaExtension extends Extension
$arguments[] = array(new Reference($callbackId), 'serialize'); $arguments[] = array(new Reference($callbackId), 'serialize');
} else { } else {
$abstractId = 'fos_elastica.object_persister'; $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); $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()) public function populate(\Closure $loggerClosure = null, array $options = array())
{ {
@ -56,34 +56,19 @@ abstract class AbstractProvider extends BaseAbstractProvider
$manager = $this->managerRegistry->getManagerForClass($this->objectClass); $manager = $this->managerRegistry->getManagerForClass($this->objectClass);
for (; $offset < $nbObjects; $offset += $batchSize) { for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset); $objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
if ($loggerClosure) {
$stepNbObjects = count($objects);
}
$objects = array_filter($objects, array($this, 'isObjectIndexable')); $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']) { if ($objects) {
$manager->clear(); if (!$ignoreErrors) {
}
continue;
}
if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
} else {
try {
$this->objectPersister->insertMany($objects); $this->objectPersister->insertMany($objects);
} catch(BulkResponseException $e) { } else {
if ($loggerClosure) { try {
$loggerClosure(sprintf('<error>%s</error>',$e->getMessage())); $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); usleep($sleep);
if ($loggerClosure) { if ($loggerClosure) {
$stepCount = $stepNbObjects + $offset; $loggerClosure($batchSize, $nbObjects);
$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()));
} }
} }

View file

@ -17,24 +17,16 @@ use Symfony\Component\EventDispatcher\Event;
* *
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv> * @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 PRE_INDEX_POPULATE = 'elastica.index.index_pre_populate';
const POST_INDEX_POPULATE = 'elastica.index.index_post_populate'; const POST_INDEX_POPULATE = 'elastica.index.index_post_populate';
const PRE_TYPE_POPULATE = 'elastica.index.type_pre_populate';
const POST_TYPE_POPULATE = 'elastica.index.type_post_populate';
/** /**
* @var string * @var string
*/ */
private $index; private $index;
/**
* @var string
*/
private $type;
/** /**
* @var bool * @var bool
*/ */
@ -47,14 +39,12 @@ class PopulateEvent extends Event
/** /**
* @param string $index * @param string $index
* @param string|null $type
* @param boolean $reset * @param boolean $reset
* @param array $options * @param array $options
*/ */
public function __construct($index, $type, $reset, $options) public function __construct($index, $reset, $options)
{ {
$this->index = $index; $this->index = $index;
$this->type = $type;
$this->reset = $reset; $this->reset = $reset;
$this->options = $options; $this->options = $options;
} }
@ -67,14 +57,6 @@ class PopulateEvent extends Event
return $this->index; return $this->index;
} }
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/** /**
* @return boolean * @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) public function buildTypeMapping(TypeConfig $typeConfig)
{ {
$mapping = array_merge($typeConfig->getMapping(), array( $mapping = $typeConfig->getMapping();
// 'date_detection' => true,
// 'dynamic_date_formats' => array() if (null !== $typeConfig->getDynamicDateFormats()) {
// 'dynamic_templates' => $typeConfig->getDynamicTemplates(), $mapping['dynamic_date_formats'] = $typeConfig->getDynamicDateFormats();
// 'numeric_detection' => false, }
// 'properties' => array(),
)); if (null !== $typeConfig->getDateDetection()) {
$mapping['date_detection'] = $typeConfig->getDateDetection();
}
if (null !== $typeConfig->getNumericDetection()) {
$mapping['numeric_detection'] = $typeConfig->getNumericDetection();
}
if ($typeConfig->getIndexAnalyzer()) { if ($typeConfig->getIndexAnalyzer()) {
$mapping['index_analyzer'] = $typeConfig->getIndexAnalyzer(); $mapping['index_analyzer'] = $typeConfig->getIndexAnalyzer();
@ -104,9 +110,14 @@ class MappingBuilder
private function fixProperties(&$properties) private function fixProperties(&$properties)
{ {
foreach ($properties as $name => &$property) { foreach ($properties as $name => &$property) {
unset($property['property_path']);
if (!isset($property['type'])) { if (!isset($property['type'])) {
$property['type'] = 'string'; $property['type'] = 'string';
} }
if ($property['type'] == 'multi_field' && isset($property['fields'])) {
$this->fixProperties($property['fields']);
}
if (isset($property['properties'])) { if (isset($property['properties'])) {
$this->fixProperties($property['properties']); $this->fixProperties($property['properties']);
} }

View file

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

View file

@ -12,7 +12,7 @@ use FOS\ElasticaBundle\Provider\AbstractProvider;
class Provider extends AbstractProvider class Provider extends AbstractProvider
{ {
/** /**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate() * {@inheritDoc}
*/ */
public function populate(\Closure $loggerClosure = null, array $options = array()) 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']; $batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
for (; $offset < $nbObjects; $offset += $batchSize) { for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $queryClass::create() $objects = $queryClass::create()
->limit($batchSize) ->limit($batchSize)
->offset($offset) ->offset($offset)
->find() ->find()
->getArrayCopy(); ->getArrayCopy();
if ($loggerClosure) {
$stepNbObjects = count($objects);
}
$objects = array_filter($objects, array($this, 'isObjectIndexable')); $objects = array_filter($objects, array($this, 'isObjectIndexable'));
if (!$objects) { if ($objects) {
$loggerClosure('<info>Entire batch was filtered away, skipping...</info>'); $this->objectPersister->insertMany($objects);
continue;
} }
$this->objectPersister->insertMany($objects);
usleep($sleep); usleep($sleep);
if ($loggerClosure) { if ($loggerClosure) {
$stepCount = $stepNbObjects + $offset; $loggerClosure($batchSize, $nbObjects);
$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()));
} }
} }
} }

View file

@ -70,6 +70,7 @@ abstract class AbstractProvider implements ProviderInterface
/** /**
* Get string with RAM usage information (current and peak) * Get string with RAM usage information (current and peak)
* *
* @deprecated To be removed in 4.0
* @return string * @return string
*/ */
protected function getMemoryUsage() protected function getMemoryUsage()

View file

@ -58,7 +58,7 @@ class ProviderRegistry implements ContainerAwareInterface
* Providers will be indexed by "type" strings in the returned array. * Providers will be indexed by "type" strings in the returned array.
* *
* @param string $index * @param string $index
* @return array of ProviderInterface instances * @return ProviderInterface[]
* @throws \InvalidArgumentException if no providers were registered for the index * @throws \InvalidArgumentException if no providers were registered for the index
*/ */
public function getIndexProviders($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"> <service id="fos_elastica.listener.prototype.mongodb" class="%fos_elastica.listener.prototype.mongodb.class%" public="false" abstract="true">
<argument /> <!-- object persister --> <argument /> <!-- object persister -->
<argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration --> <argument type="collection" /> <!-- configuration -->
<argument /> <!-- logger --> <argument /> <!-- logger -->

View file

@ -12,7 +12,8 @@
<services> <services>
<service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true"> <service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true">
<argument type="collection" /> <!-- options --> <argument type="collection" /> <!-- options -->
<argument type="service" id="event_dispatcher" /> <!-- options -->
<call method="setPropertyAccessor"> <call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" /> <argument type="service" id="fos_elastica.property_accessor" />
</call> </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 # app/config/config.yml
services: services:
acme.search_provider.user: acme.search_provider.user:
class: Acme\UserBundle\Search\UserProvider class: Acme\UserBundle\Provider\UserProvider
arguments: arguments:
- @fos_elastica.index.website.user - @fos_elastica.index.website.user
tags: tags:

View file

@ -11,6 +11,11 @@ fos_elastica:
connections: connections:
- url: http://es1.example.net:9200 - url: http://es1.example.net:9200
- url: http://es2.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 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\Exception\ExceptionInterface;
use Elastica\Request; use Elastica\Request;
use Elastica\Response; use Elastica\Response;
use FOS\ElasticaBundle\Client as BaseClient; use FOS\ElasticaBundle\Elastica\Client as BaseClient;
class Client extends BaseClient class Client extends BaseClient
{ {

View file

@ -13,6 +13,7 @@ Cookbook Entries
---------------- ----------------
* [Aliased Indexes](cookbook/aliased-indexes.md) * [Aliased Indexes](cookbook/aliased-indexes.md)
* [Custom Indexed Properties](cookbook/custom-properties.md)
* [Custom Repositories](cookbook/custom-repositories.md) * [Custom Repositories](cookbook/custom-repositories.md)
* [HTTP Headers for Elastica](cookbook/elastica-client-http-headers.md) * [HTTP Headers for Elastica](cookbook/elastica-client-http-headers.md)
* Performance - [Logging](cookbook/logging.md) * Performance - [Logging](cookbook/logging.md)

View file

@ -1,40 +1,50 @@
Step 1: Setting up the bundle 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 ```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 ### Elasticsearch
Instructions for installing and deploying Elasticsearch may be found Instructions for installing and deploying Elasticsearch may be found [here](http://www.elasticsearch.org/guide/reference/setup/installation/).
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
Step 2: Enable the Bundle
-------------------------
B) Enable FOSElasticaBundle Then, enable the bundle by adding the following line in the `app/AppKernel.php`
--------------------------- file of your project:
Enable FOSElasticaBundle in your AppKernel:
```php ```php
<?php <?php
// app/AppKernel.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 The basic minimal configuration for FOSElasticaBundle is one client with one Elasticsearch
@ -48,27 +58,30 @@ fos_elastica:
clients: clients:
default: { host: localhost, port: 9200 } default: { host: localhost, port: 9200 }
indexes: indexes:
search: ~ app: ~
``` ```
In this example, an Elastica index (an instance of `Elastica\Index`) is available as a 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 You may want the index `app` to be named something else on ElasticSearch depending on
application, for example, renaming the search index based on different environments. 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 ```yaml
#app/config/config.yml #app/config/config.yml
fos_elastica: fos_elastica:
indexes: indexes:
search: app:
index_name: search_dev index_name: app_%kernel.env%
``` ```
In this case, the service `fos_elastica.index.search` will be using an Elasticsearch In this case, the service `fos_elastica.index.app` will relate to an ElasticSearch index
index of search_dev. 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. By default, FOSElasticaBundle requires each type that is to be indexed to be mapped.
@ -81,7 +94,7 @@ will end up being indexed.
```yaml ```yaml
fos_elastica: fos_elastica:
indexes: indexes:
search: app:
types: types:
user: user:
mappings: mappings:
@ -92,7 +105,7 @@ fos_elastica:
``` ```
Each defined type is made available as a service, and in this case the service key is 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 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 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 There are a significant number of options available for types, that can be
[found here](types.md) [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 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 The command will also create all indexes and types defined if they do not already exist
on the Elasticsearch server. on the Elasticsearch server.
F) Usage F: Usage
-------- --------
Usage documentation for the bundle is available [here](usage.md) Usage documentation for the bundle is available [here](usage.md)

View file

@ -1,6 +1,34 @@
Type configuration 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 Handling missing results with FOSElasticaBundle
----------------------------------------------- -----------------------------------------------

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -23,7 +23,7 @@
{% block menu %} {% block menu %}
<span class="label"> <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> <strong>Elastica</strong>
<span class="count"> <span class="count">
<span>{{ collector.querycount }}</span> <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->assertTrue($mapping['type']['properties']['field1']['store']);
$this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']); $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'); $parent = $this->getType($client, 'parent');
$mapping = $parent->getMapping(); $mapping = $parent->getMapping();
@ -49,6 +53,9 @@ class MappingToElasticaTest extends WebTestCase
$mapping = $type->getMapping(); $mapping = $type->getMapping();
$this->assertNotEmpty($mapping, 'Mapping was populated'); $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->assertArrayHasKey('store', $mapping['type']['properties']['field1']);
$this->assertTrue($mapping['type']['properties']['field1']['store']); $this->assertTrue($mapping['type']['properties']['field1']['store']);
$this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']); $this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']);
@ -105,6 +112,7 @@ class MappingToElasticaTest extends WebTestCase
/** /**
* @param Client $client * @param Client $client
* @param string $type
* @return \Elastica\Type * @return \Elastica\Type
*/ */
private function getType(Client $client, $type = '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 class TypeObj
{ {
public $id = 5;
public $coll; public $coll;
public $field1; public $field1;
public $field2;
public function isIndexable() public function isIndexable()
{ {

View file

@ -15,7 +15,12 @@ fos_elastica:
- url: http://localhost:9200 - url: http://localhost:9200
- host: localhost - host: localhost
port: 9200 port: 9200
connectionStrategy: RoundRobin
second_server: second_server:
connections:
- url: http://localhost:9200
connection_strategy: RoundRobin
third:
url: http://localhost:9200 url: http://localhost:9200
indexes: indexes:
index: index:
@ -46,6 +51,8 @@ fos_elastica:
index_analyzer: my_analyzer index_analyzer: my_analyzer
type: type:
search_analyzer: my_analyzer search_analyzer: my_analyzer
date_detection: false
dynamic_date_formats: [ 'yyyy-MM-dd' ]
dynamic_templates: dynamic_templates:
- dates: - dates:
match: "date_*" match: "date_*"
@ -56,6 +63,7 @@ fos_elastica:
mapping: mapping:
analyzer: english analyzer: english
type: string type: string
numeric_detection: true
properties: properties:
field1: ~ field1: ~
field2: field2:
@ -71,6 +79,11 @@ fos_elastica:
properties: properties:
date: { boost: 5 } date: { boost: 5 }
content: ~ content: ~
multiple:
type: "multi_field"
properties:
name: ~
position: ~
user: user:
type: "object" type: "object"
approver: approver:
@ -87,3 +100,4 @@ fos_elastica:
identifier: "id" identifier: "id"
null_mappings: null_mappings:
mappings: ~ mappings: ~
empty_index: ~

View file

@ -65,6 +65,18 @@ fos_elastica:
provider: ~ provider: ~
listener: listener:
is_indexable_callback: [ 'FOS\ElasticaBundle\Tests\Functional\app\ORM\IndexableService', 'isntIndexable' ] 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: second_index:
index_name: foselastica_orm_test_second_%kernel.environment% index_name: foselastica_orm_test_second_%kernel.environment%
types: types:

View file

@ -28,7 +28,7 @@ fos_elastica:
serializer: ~ serializer: ~
indexes: indexes:
index: index:
index_name: foselastica_test_%kernel.environment% index_name: foselastica_ser_test_%kernel.environment%
types: types:
type: type:
properties: properties:

View file

@ -2,6 +2,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer; namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer;
use FOS\ElasticaBundle\Event\TransformEvent;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer; use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess; 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() public function testThatCanTransformObject()
{ {
$transformer = $this->getTransformer(); $transformer = $this->getTransformer();
@ -250,7 +280,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$document = $transformer->transform(new POPO(), array( $document = $transformer->transform(new POPO(), array(
'sub' => array( 'sub' => array(
'type' => 'nested', 'type' => 'nested',
'properties' => array('foo' => '~') 'properties' => array('foo' => array())
) )
)); ));
$data = $document->getData(); $data = $document->getData();
@ -295,8 +325,8 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(array_key_exists('obj', $data)); $this->assertTrue(array_key_exists('obj', $data));
$this->assertInternalType('array', $data['obj']); $this->assertInternalType('array', $data['obj']);
$this->assertEquals(array( $this->assertEquals(array(
'foo' => 'foo', 'foo' => 'foo',
'bar' => 'foo', 'bar' => 'foo',
'id' => 1 'id' => 1
), $data['obj']); ), $data['obj']);
} }
@ -387,11 +417,12 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @param null|\Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* @return ModelToElasticaAutoTransformer * @return ModelToElasticaAutoTransformer
*/ */
private function getTransformer() private function getTransformer($dispatcher = null)
{ {
$transformer = new ModelToElasticaAutoTransformer(); $transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher);
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
return $transformer; return $transformer;

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Transformer; namespace FOS\ElasticaBundle\Transformer;
use FOS\ElasticaBundle\Event\TransformEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Elastica\Document; use Elastica\Document;
@ -12,6 +14,11 @@ use Elastica\Document;
*/ */
class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterface class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterface
{ {
/**
* @var EventDispatcherInterface
*/
protected $dispatcher;
/** /**
* Optional parameters * Optional parameters
* *
@ -32,10 +39,12 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
* Instanciates a new Mapper * Instanciates a new Mapper
* *
* @param array $options * @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->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']; $property = (null !== $mapping['property'])?$mapping['property']:$mapping['type'];
$value = $this->propertyAccessor->getValue($object, $property); $value = $this->propertyAccessor->getValue($object, $property);
$document->setParent($this->propertyAccessor->getValue($value, $mapping['identifier'])); $document->setParent($this->propertyAccessor->getValue($value, $mapping['identifier']));
continue; 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'])) { 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 /* $value is a nested document or object. Transform $value into
* an array of documents, respective the mapped properties. * an array of documents, respective the mapped properties.
*/ */
$document->set($key, $this->transformNested($value, $mapping['properties'])); $document->set($key, $this->transformNested($value, $mapping['properties']));
continue; continue;
} }
@ -86,12 +103,20 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
} else { } else {
$document->addFileContent($key, $value); $document->addFileContent($key, $value);
} }
continue; continue;
} }
$document->set($key, $this->normalizeValue($value)); $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; return $document;
} }

View file

@ -17,7 +17,7 @@
"symfony/console": "~2.1", "symfony/console": "~2.1",
"symfony/form": "~2.1", "symfony/form": "~2.1",
"symfony/property-access": "~2.2", "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" "psr/log": "~1.0"
}, },
"require-dev":{ "require-dev":{