diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 0000000..e2cb043
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,5 @@
+imports:
+ - php
+
+tools:
+ external_code_coverage: true
diff --git a/.travis.yml b/.travis.yml
index a884d86..fbb22d1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,36 @@
language: php
+cache:
+ directories:
+ - $HOME/.composer/cache
+
php:
- 5.3
- 5.4
- 5.5
+ - 5.6
+
+matrix:
+ include:
+ - php: 5.5
+ env: SYMFONY_VERSION='2.3.*'
+ - php: 5.5
+ env: SYMFONY_VERSION='2.5.*'
before_script:
+ - /usr/share/elasticsearch/bin/elasticsearch -v
- sudo /usr/share/elasticsearch/bin/plugin -install elasticsearch/elasticsearch-mapper-attachments/2.0.0
- sudo service elasticsearch restart
- - echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+ - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'
+ - 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
+script:
+ - vendor/bin/phpunit --coverage-clover=coverage.clover
services:
- elasticsearch
+
+after_script:
+ - wget https://scrutinizer-ci.com/ocular.phar
+ - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md
index 2596a29..57ca45c 100644
--- a/CHANGELOG-3.0.md
+++ b/CHANGELOG-3.0.md
@@ -12,6 +12,29 @@ 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.9 (2015-03-12)
+
+ * Fix a bug in the BC layer of the type configuration for empty configs
+ * Fix the service definition for the Doctrine listener when the logger is not enabled
+
+* 3.0.8 (2014-01-31)
+
+ * Fixed handling of empty indexes #760
+ * Added support for `connectionStrategy` Elastica configuration #732
+ * Allow Elastica 1.4
+
+* 3.0.7 (2015-01-21)
+
+ * Fixed the indexing of parent/child relations, broken since 3.0 #774
+ * Fixed multi_field properties not being normalised #769
+
+* 3.0.6 (2015-01-04)
+
+ * Removed unused public image asset for the web development toolbar #742
+ * Fixed is_indexable_callback BC code to support array notation #761
+ * Fixed debug_logger for type providers #724
+ * Clean the OM if we filter away the entire batch #737
+
* 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called
diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md
new file mode 100644
index 0000000..3ca3c77
--- /dev/null
+++ b/CHANGELOG-3.1.md
@@ -0,0 +1,61 @@
+CHANGELOG for 3.1.x
+===================
+
+This changelog references the relevant changes (bug and security fixes) done
+in 3.1 versions.
+
+To get the diff for a specific change, go to
+https://github.com/FriendsOfSymfony/FOSElasticaBundle/commit/XXX where XXX is
+the commit hash. To get the diff between two versions, go to
+https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
+
+* 3.1.3 (2015-04-02)
+
+ * Fix Symfony 2.3 compatibility
+
+* 3.1.2 (2015-03-27)
+
+ * Fix the previous release
+
+* 3.1.1 (2015-03-27)
+
+ * Fix PopulateCommand trying to set formats for ProgressBar in Symfony < 2.5
+ * Fix Provider implementations that depend on a batch size from going into
+ infinite loops
+
+* 3.1.0 (2015-03-18)
+
+ * BC BREAK: `Doctrine\Listener#scheduleForDeletion` access changed to private.
+ * BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
+ returns a boolean value if it will handle a given object or not.
+ * BC BREAK: Removed `Doctrine\Listener#getSubscribedEvents`. The container
+ configuration now configures tags with the methods to call to avoid loading
+ this class on every request where doctrine is active. #729
+ * BC BREAK: Added methods for retrieving aggregations when paginating results.
+ The `PaginationAdapterInterface` has a new method, `getAggregations`. #726
+ * Added ability to configure `date_detection`, `numeric_detection` and
+ `dynamic_date_formats` for types. #753
+ * New event `POST_TRANSFORM` which allows developers to add custom properties to
+ Elastica Documents for indexing.
+ * When available, the `fos:elastica:populate` command will now use the
+ ProgressBar helper instead of outputting strings. You can use verbosity
+ controls on the command to output additional information like memory
+ usage, runtime and estimated time.
+ * Added new option `property_path` to a type property definition to allow
+ customisation of the property path used to retrieve data from objects.
+ Setting `property_path` to `false` will configure the Transformer to ignore
+ that property while transforming. Combined with the above POST_TRANSFORM event
+ developers can now create calculated dynamic properties on Elastica documents
+ for indexing. #794
+ * Fixed a case where ProgressCommand would always ignore errors regardless of
+ --ignore-errors being passed or not.
+ * Added a `SliceFetcher` abstraction for Doctrine providers that get more
+ information about the previous slice allowing for optimising queries during
+ population. #725
+ * New events `PRE_INDEX_POPULATE`, `POST_INDEX_POPULATE`, `PRE_TYPE_POPULATE` and
+ `POST_TYPE_POPULATE` allow for monitoring when an index is about to be or has
+ just been populated. #744
+ * New events `PRE_INDEX_RESET`, `POST_INDEX_RESET`, `PRE_TYPE_RESET` and
+ `POST_TYPE_RESET` are run before and after operations that will reset an
+ index. #744
+ * Added indexable callback support for the __invoke method of a service. #823
diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php
index f17ca4c..42af355 100644
--- a/Command/PopulateCommand.php
+++ b/Command/PopulateCommand.php
@@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Command;
+use FOS\ElasticaBundle\Event\IndexPopulateEvent;
+use FOS\ElasticaBundle\Event\TypePopulateEvent;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
@@ -10,18 +12,28 @@ use Symfony\Component\Console\Output\OutputInterface;
use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Provider\ProviderRegistry;
use FOS\ElasticaBundle\Resetter;
-use FOS\ElasticaBundle\Provider\ProviderInterface;
+use Symfony\Component\Console\Helper\ProgressBar;
/**
- * Populate the search index
+ * Populate the search index.
*/
class PopulateCommand extends ContainerAwareCommand
{
+ /**
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ private $dispatcher;
+
/**
* @var IndexManager
*/
private $indexManager;
+ /**
+ * @var ProgressClosureBuilder
+ */
+ private $progressClosureBuilder;
+
/**
* @var ProviderRegistry
*/
@@ -46,31 +58,46 @@ class PopulateCommand extends ContainerAwareCommand
->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Sleep time between persisting iterations (microseconds)', 0)
->addOption('batch-size', null, InputOption::VALUE_REQUIRED, 'Index packet size (overrides provider config option)')
->addOption('ignore-errors', null, InputOption::VALUE_NONE, 'Do not stop on errors')
+ ->addOption('no-overwrite-format', null, InputOption::VALUE_NONE, 'Prevent this command from overwriting ProgressBar\'s formats')
->setDescription('Populates search indexes from providers')
;
}
/**
- * @see Symfony\Component\Console\Command\Command::initialize()
+ * {@inheritDoc}
*/
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->resetter = $this->getContainer()->get('fos_elastica.resetter');
+ $this->progressClosureBuilder = new ProgressClosureBuilder();
+
+ if (!$input->getOption('no-overwrite-format') && class_exists('Symfony\\Component\\Console\\Helper\\ProgressBar')) {
+ 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%");
+ }
}
/**
- * @see Symfony\Component\Console\Command\Command::execute()
+ * {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
- $index = $input->getOption('index');
- $type = $input->getOption('type');
- $reset = !$input->getOption('no-reset');
- $options = $input->getOptions();
-
- $options['ignore-errors'] = $input->hasOption('ignore-errors');
+ $index = $input->getOption('index');
+ $type = $input->getOption('type');
+ $reset = !$input->getOption('no-reset');
+ $options = array(
+ 'ignore_errors' => $input->getOption('ignore-errors'),
+ 'offset' => $input->getOption('offset'),
+ 'sleep' => $input->getOption('sleep')
+ );
+ if ($input->getOption('batch-size')) {
+ $options['batch_size'] = (int) $input->getOption('batch-size');
+ }
if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */
@@ -109,25 +136,22 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndex(OutputInterface $output, $index, $reset, $options)
{
- if ($reset) {
+ $event = new IndexPopulateEvent($index, $reset, $options);
+ $this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event);
+
+ if ($event->isReset()) {
$output->writeln(sprintf('Resetting %s', $index));
$this->resetter->resetIndex($index, true);
}
- /** @var $providers ProviderInterface[] */
- $providers = $this->providerRegistry->getIndexProviders($index);
-
- foreach ($providers as $type => $provider) {
- $loggerClosure = function($message) use ($output, $index, $type) {
- $output->writeln(sprintf('Populating %s/%s, %s', $index, $type, $message));
- };
-
- $provider->populate($loggerClosure, $options);
+ $types = array_keys($this->providerRegistry->getIndexProviders($index));
+ foreach ($types as $type) {
+ $this->populateIndexType($output, $index, $type, false, $event->getOptions());
}
- $output->writeln(sprintf('Refreshing %s', $index));
- $this->resetter->postPopulate($index);
- $this->indexManager->getIndex($index)->refresh();
+ $this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);
+
+ $this->refreshIndex($output, $index);
}
/**
@@ -141,17 +165,35 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options)
{
- if ($reset) {
+ $event = new TypePopulateEvent($index, $type, $reset, $options);
+ $this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event);
+
+ if ($event->isReset()) {
$output->writeln(sprintf('Resetting %s/%s', $index, $type));
$this->resetter->resetIndexType($index, $type);
}
- $loggerClosure = function($message) use ($output, $index, $type) {
- $output->writeln(sprintf('Populating %s/%s, %s', $index, $type, $message));
- };
-
$provider = $this->providerRegistry->getProvider($index, $type);
- $provider->populate($loggerClosure, $options);
+ $loggerClosure = $this->progressClosureBuilder->build($output, 'Populating', $index, $type);
+ $provider->populate($loggerClosure, $event->getOptions());
+
+ $this->dispatcher->dispatch(TypePopulateEvent::POST_TYPE_POPULATE, $event);
+
+ $this->refreshIndex($output, $index, false);
+ }
+
+ /**
+ * Refreshes an index.
+ *
+ * @param OutputInterface $output
+ * @param string $index
+ * @param bool $postPopulate
+ */
+ private function refreshIndex(OutputInterface $output, $index, $postPopulate = true)
+ {
+ if ($postPopulate) {
+ $this->resetter->postPopulate($index);
+ }
$output->writeln(sprintf('Refreshing %s', $index));
$this->indexManager->getIndex($index)->refresh();
diff --git a/Command/ProgressClosureBuilder.php b/Command/ProgressClosureBuilder.php
new file mode 100644
index 0000000..f244bc3
--- /dev/null
+++ b/Command/ProgressClosureBuilder.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Command;
+
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ProgressClosureBuilder
+{
+ /**
+ * Builds a loggerClosure to be called from inside the Provider to update the command
+ * line.
+ *
+ * @param OutputInterface $output
+ * @param string $action
+ * @param string $index
+ * @param string $type
+ *
+ * @return callable
+ */
+ public function build(OutputInterface $output, $action, $index, $type)
+ {
+ if (!class_exists('Symfony\Component\Console\Helper\ProgressBar') ||
+ !is_callable(array('Symfony\Component\Console\Helper\ProgressBar', 'getProgress'))) {
+ return $this->buildLegacy($output, $action, $index, $type);
+ }
+
+ $progress = null;
+
+ return function ($increment, $totalObjects, $message = null) use (&$progress, $output, $action, $index, $type) {
+ if (null === $progress) {
+ $progress = new ProgressBar($output, $totalObjects);
+ $progress->start();
+ }
+
+ if (null !== $message) {
+ $progress->clear();
+ $output->writeln(sprintf('%s %s', $action, $message));
+ $progress->display();
+ }
+
+ $progress->setMessage(sprintf('%s %s/%s', $action, $index, $type));
+ $progress->advance($increment);
+ };
+ }
+
+ /**
+ * Builds a legacy closure that outputs lines for each step. Used in cases
+ * where the ProgressBar component doesnt exist or does not have the correct
+ * methods to support what we need.
+ *
+ * @param OutputInterface $output
+ * @param string $action
+ * @param string $index
+ * @param string $type
+ *
+ * @return callable
+ */
+ private function buildLegacy(OutputInterface $output, $action, $index, $type)
+ {
+ $lastStep = null;
+ $current = 0;
+
+ return function ($increment, $totalObjects, $message = null) use ($output, $action, $index, $type, &$lastStep, &$current) {
+ if ($current + $increment > $totalObjects) {
+ $increment = $totalObjects - $current;
+ }
+
+ if (null !== $message) {
+ $output->writeln(sprintf('%s %s', $action, $message));
+ }
+
+ $currentTime = microtime(true);
+ $timeDifference = $currentTime - $lastStep;
+ $objectsPerSecond = $lastStep ? ($increment / $timeDifference) : $increment;
+ $lastStep = $currentTime;
+ $current += $increment;
+ $percent = 100 * $current / $totalObjects;
+
+ $output->writeln(sprintf(
+ '%s %s/%s %0.1f%% (%d/%d), %d objects/s (RAM: current=%uMo peak=%uMo)',
+ $action,
+ $index,
+ $type,
+ $percent,
+ $current,
+ $totalObjects,
+ $objectsPerSecond,
+ round(memory_get_usage() / (1024 * 1024)),
+ round(memory_get_peak_usage() / (1024 * 1024))
+ ));
+ };
+ }
+}
diff --git a/Command/ResetCommand.php b/Command/ResetCommand.php
index 06cfe48..85c5483 100755
--- a/Command/ResetCommand.php
+++ b/Command/ResetCommand.php
@@ -10,7 +10,7 @@ use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Resetter;
/**
- * Reset search indexes
+ * Reset search indexes.
*/
class ResetCommand extends ContainerAwareCommand
{
@@ -33,6 +33,7 @@ class ResetCommand extends ContainerAwareCommand
->setName('fos:elastica:reset')
->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to reset')
->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type to reset')
+ ->addOption('force', null, InputOption::VALUE_NONE, 'Force index deletion if same name as alias')
->setDescription('Reset search indexes')
;
}
@@ -51,8 +52,9 @@ class ResetCommand extends ContainerAwareCommand
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
- $index = $input->getOption('index');
- $type = $input->getOption('type');
+ $index = $input->getOption('index');
+ $type = $input->getOption('type');
+ $force = (bool) $input->getOption('force');
if (null === $index && null !== $type) {
throw new \InvalidArgumentException('Cannot specify type option without an index.');
@@ -69,7 +71,7 @@ class ResetCommand extends ContainerAwareCommand
foreach ($indexes as $index) {
$output->writeln(sprintf('Resetting %s', $index));
- $this->resetter->resetIndex($index);
+ $this->resetter->resetIndex($index, false, $force);
}
}
}
diff --git a/Command/SearchCommand.php b/Command/SearchCommand.php
index ec7cfb7..11183de 100644
--- a/Command/SearchCommand.php
+++ b/Command/SearchCommand.php
@@ -11,7 +11,7 @@ use Elastica\Query;
use Elastica\Result;
/**
- * Searches a type
+ * Searches a type.
*/
class SearchCommand extends ContainerAwareCommand
{
diff --git a/Configuration/IndexConfig.php b/Configuration/IndexConfig.php
index 7416424..749b10d 100644
--- a/Configuration/IndexConfig.php
+++ b/Configuration/IndexConfig.php
@@ -53,9 +53,9 @@ class IndexConfig
/**
* Constructor expects an array as generated by the Container Configuration builder.
*
- * @param string $name
+ * @param string $name
* @param TypeConfig[] $types
- * @param array $config
+ * @param array $config
*/
public function __construct($name, array $types, array $config)
{
@@ -92,7 +92,9 @@ class IndexConfig
/**
* @param string $typeName
+ *
* @return TypeConfig
+ *
* @throws \InvalidArgumentException
*/
public function getType($typeName)
diff --git a/Configuration/ManagerInterface.php b/Configuration/ManagerInterface.php
index 96d510f..742df1b 100644
--- a/Configuration/ManagerInterface.php
+++ b/Configuration/ManagerInterface.php
@@ -20,6 +20,7 @@ interface ManagerInterface
* Returns configuration for an index.
*
* @param $index
+ *
* @return IndexConfig
*/
public function getIndexConfiguration($index);
@@ -36,6 +37,7 @@ interface ManagerInterface
*
* @param string $index
* @param string $type
+ *
* @return TypeConfig
*/
public function getTypeConfiguration($index, $type);
diff --git a/Configuration/Search.php b/Configuration/Search.php
index 1306f92..1d046c0 100644
--- a/Configuration/Search.php
+++ b/Configuration/Search.php
@@ -17,9 +17,10 @@ use FOS\ElasticaBundle\Annotation\Search as BaseSearch;
* Annotation class for setting search repository.
*
* @Annotation
+ *
* @deprecated Use FOS\ElasticaBundle\Annotation\Search instead
* @Target("CLASS")
*/
class Search extends BaseSearch
{
-}
+}
diff --git a/Configuration/Source/ContainerSource.php b/Configuration/Source/ContainerSource.php
index 8d094c7..25e6f86 100644
--- a/Configuration/Source/ContainerSource.php
+++ b/Configuration/Source/ContainerSource.php
@@ -40,16 +40,7 @@ class ContainerSource implements SourceInterface
{
$indexes = array();
foreach ($this->configArray as $config) {
- $types = array();
- foreach ($config['types'] as $typeConfig) {
- $types[$typeConfig['name']] = new TypeConfig(
- $typeConfig['name'],
- $typeConfig['mapping'],
- $typeConfig['config']
- );
- // TODO: handle prototypes..
- }
-
+ $types = $this->getTypes($config);
$index = new IndexConfig($config['name'], $types, array(
'elasticSearchName' => $config['elasticsearch_name'],
'settings' => $config['settings'],
@@ -61,4 +52,29 @@ class ContainerSource implements SourceInterface
return $indexes;
}
+
+ /**
+ * Builds TypeConfig objects for each type.
+ *
+ * @param array $config
+ *
+ * @return array
+ */
+ protected function getTypes($config)
+ {
+ $types = array();
+
+ if (isset($config['types'])) {
+ foreach ($config['types'] as $typeConfig) {
+ $types[$typeConfig['name']] = new TypeConfig(
+ $typeConfig['name'],
+ $typeConfig['mapping'],
+ $typeConfig['config']
+ );
+ // TODO: handle prototypes..
+ }
+ }
+
+ return $types;
+ }
}
diff --git a/Configuration/Source/SourceInterface.php b/Configuration/Source/SourceInterface.php
index 34e9901..05a64d0 100644
--- a/Configuration/Source/SourceInterface.php
+++ b/Configuration/Source/SourceInterface.php
@@ -23,4 +23,4 @@ interface SourceInterface
* @return \FOS\ElasticaBundle\Configuration\IndexConfig[]
*/
public function getConfiguration();
-}
+}
diff --git a/Configuration/TypeConfig.php b/Configuration/TypeConfig.php
index 5d3f084..a46cd34 100644
--- a/Configuration/TypeConfig.php
+++ b/Configuration/TypeConfig.php
@@ -35,6 +35,30 @@ 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
+ */
+ public function getIndexAnalyzer()
+ {
+ return $this->getConfig('index_analyzer');
+ }
+
/**
* @return array
*/
@@ -43,6 +67,9 @@ class TypeConfig
return $this->mapping;
}
+ /**
+ * @return string|null
+ */
public function getModel()
{
return isset($this->config['persistence']['model']) ?
@@ -50,6 +77,14 @@ class TypeConfig
null;
}
+ /**
+ * @return bool|null
+ */
+ public function getNumericDetection()
+ {
+ return $this->getConfig('numeric_detection');
+ }
+
/**
* @return string
*/
@@ -57,4 +92,22 @@ class TypeConfig
{
return $this->name;
}
+
+ /**
+ * @return string|null
+ */
+ public function getSearchAnalyzer()
+ {
+ return $this->getConfig('search_analyzer');
+ }
+
+ /**
+ * @param string $key
+ */
+ private function getConfig($key)
+ {
+ return isset($this->config[$key]) ?
+ $this->config[$key] :
+ null;
+ }
}
diff --git a/DependencyInjection/Compiler/ConfigSourcePass.php b/DependencyInjection/Compiler/ConfigSourcePass.php
index b35a665..92a2489 100644
--- a/DependencyInjection/Compiler/ConfigSourcePass.php
+++ b/DependencyInjection/Compiler/ConfigSourcePass.php
@@ -33,4 +33,4 @@ class ConfigSourcePass implements CompilerPassInterface
$container->getDefinition('fos_elastica.config_manager')->replaceArgument(0, $sources);
}
-}
+}
diff --git a/DependencyInjection/Compiler/RegisterProvidersPass.php b/DependencyInjection/Compiler/RegisterProvidersPass.php
index c6c9e6e..4fd25b0 100644
--- a/DependencyInjection/Compiler/RegisterProvidersPass.php
+++ b/DependencyInjection/Compiler/RegisterProvidersPass.php
@@ -55,6 +55,7 @@ class RegisterProvidersPass implements CompilerPassInterface
* Returns whether the class implements ProviderInterface.
*
* @param string $class
+ *
* @return boolean
*/
private function isProviderImplementation($class)
diff --git a/DependencyInjection/Compiler/TransformerPass.php b/DependencyInjection/Compiler/TransformerPass.php
index 4281d0b..596c732 100644
--- a/DependencyInjection/Compiler/TransformerPass.php
+++ b/DependencyInjection/Compiler/TransformerPass.php
@@ -31,7 +31,7 @@ class TransformerPass implements CompilerPassInterface
throw new InvalidArgumentException('The Transformer must have both a type and an index defined.');
}
- $transformers[$tag['index']][$tag['type']]= new Reference($id);
+ $transformers[$tag['index']][$tag['type']] = new Reference($id);
}
}
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 874f51e..1391eaa 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -30,7 +30,7 @@ class Configuration implements ConfigurationInterface
/**
* Generates the configuration tree.
*
- * @return \Symfony\Component\Config\Definition\NodeInterface
+ * @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
@@ -63,7 +63,7 @@ class Configuration implements ConfigurationInterface
}
/**
- * Adds the configuration for the "clients" key
+ * Adds the configuration for the "clients" key.
*/
private function addClientsSection(ArrayNodeDefinition $rootNode)
{
@@ -73,22 +73,33 @@ class Configuration implements ConfigurationInterface
->arrayNode('clients')
->useAttributeAsKey('id')
->prototype('array')
+ ->performNoDeepMerging()
// BC - Renaming 'servers' node to 'connections'
->beforeNormalization()
- ->ifTrue(function($v) { return isset($v['servers']); })
- ->then(function($v) {
+ ->ifTrue(function ($v) { return isset($v['servers']); })
+ ->then(function ($v) {
$v['connections'] = $v['servers'];
unset($v['servers']);
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); })
->then(function ($v) {
return array(
- 'connections' => array($v)
+ 'connections' => array($v),
);
})
->end()
@@ -100,8 +111,8 @@ class Configuration implements ConfigurationInterface
->children()
->scalarNode('url')
->validate()
- ->ifTrue(function($url) { return $url && substr($url, -1) !== '/'; })
- ->then(function($url) { return $url.'/'; })
+ ->ifTrue(function ($url) { return $url && substr($url, -1) !== '/'; })
+ ->then(function ($url) { return $url.'/'; })
->end()
->end()
->scalarNode('host')->end()
@@ -123,6 +134,7 @@ class Configuration implements ConfigurationInterface
->end()
->scalarNode('timeout')->end()
->scalarNode('headers')->end()
+ ->scalarNode('connectionStrategy')->defaultValue('Simple')->end()
->end()
->end()
->end()
@@ -131,7 +143,7 @@ class Configuration implements ConfigurationInterface
}
/**
- * Adds the configuration for the "indexes" key
+ * Adds the configuration for the "indexes" key.
*/
private function addIndexesSection(ArrayNodeDefinition $rootNode)
{
@@ -180,10 +192,14 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('name')
->prototype('array')
->treatNullLike(array())
+ ->beforeNormalization()
+ ->ifNull()
+ ->thenEmptyArray()
+ ->end()
// BC - Renaming 'mappings' node to 'properties'
->beforeNormalization()
- ->ifTrue(function($v) { return isset($v['mappings']); })
- ->then(function($v) {
+ ->ifTrue(function ($v) { return array_key_exists('mappings', $v); })
+ ->then(function ($v) {
$v['properties'] = $v['mappings'];
unset($v['mappings']);
@@ -198,7 +214,17 @@ class Configuration implements ConfigurationInterface
isset($v['persistence']['listener']['is_indexable_callback']);
})
->then(function ($v) {
- $v['indexable_callback'] = $v['persistence']['listener']['is_indexable_callback'];
+ $callback = $v['persistence']['listener']['is_indexable_callback'];
+
+ if (is_array($callback)) {
+ list($class) = $callback + array(null);
+
+ if ($class[0] !== '@' && is_string($class) && !class_exists($class)) {
+ $callback[0] = '@'.$class;
+ }
+ }
+
+ $v['indexable_callback'] = $callback;
unset($v['persistence']['listener']['is_indexable_callback']);
return $v;
@@ -213,7 +239,7 @@ class Configuration implements ConfigurationInterface
foreach ($v['dynamic_templates'] as $key => $type) {
if (is_int($key)) {
$dt[] = $type;
- } else {
+ } else {
$dt[][$key] = $type;
}
}
@@ -224,7 +250,10 @@ class Configuration implements ConfigurationInterface
})
->end()
->children()
+ ->booleanNode('date_detection')->end()
+ ->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end()
->scalarNode('index_analyzer')->end()
+ ->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end()
->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode())
@@ -393,7 +422,7 @@ class Configuration implements ConfigurationInterface
}
/**
- * Returns the array node used for "_all"
+ * Returns the array node used for "_all".
*/
protected function getAllNode()
{
@@ -412,7 +441,7 @@ class Configuration implements ConfigurationInterface
}
/**
- * Returns the array node used for "_timestamp"
+ * Returns the array node used for "_timestamp".
*/
protected function getTimestampNode()
{
@@ -433,7 +462,7 @@ class Configuration implements ConfigurationInterface
}
/**
- * Returns the array node used for "_ttl"
+ * Returns the array node used for "_ttl".
*/
protected function getTtlNode()
{
@@ -462,9 +491,9 @@ class Configuration implements ConfigurationInterface
$node
->validate()
- ->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
+ ->ifTrue(function ($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
- ->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
+ ->ifTrue(function ($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
@@ -479,9 +508,13 @@ class Configuration implements ConfigurationInterface
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
- ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
+ ->scalarNode('debug_logging')
+ ->defaultValue($this->debug)
+ ->treatNullLike(true)
+ ->end()
+ ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php
index 292a9a5..ec45e25 100644
--- a/DependencyInjection/FOSElasticaExtension.php
+++ b/DependencyInjection/FOSElasticaExtension.php
@@ -80,9 +80,10 @@ class FOSElasticaExtension extends Extension
}
/**
- * @param array $config
+ * @param array $config
* @param ContainerBuilder $container
- * @return Configuration|null|\Symfony\Component\Config\Definition\ConfigurationInterface
+ *
+ * @return Configuration
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
@@ -92,8 +93,9 @@ class FOSElasticaExtension extends Extension
/**
* Loads the configured clients.
*
- * @param array $clients An array of clients configurations
+ * @param array $clients An array of clients configurations
* @param ContainerBuilder $container A ContainerBuilder instance
+ *
* @return array
*/
private function loadClients(array $clients, ContainerBuilder $container)
@@ -108,12 +110,13 @@ class FOSElasticaExtension extends Extension
if (false !== $logger) {
$clientDef->addMethodCall('setLogger', array(new Reference($logger)));
}
+ $clientDef->addTag('fos_elastica.client');
$container->setDefinition($clientId, $clientDef);
$this->clients[$name] = array(
'id' => $clientId,
- 'reference' => new Reference($clientId)
+ 'reference' => new Reference($clientId),
);
}
}
@@ -121,16 +124,20 @@ class FOSElasticaExtension extends Extension
/**
* Loads the configured indexes.
*
- * @param array $indexes An array of indexes configurations
+ * @param array $indexes An array of indexes configurations
* @param ContainerBuilder $container A ContainerBuilder instance
+ *
* @throws \InvalidArgumentException
+ *
* @return array
*/
private function loadIndexes(array $indexes, ContainerBuilder $container)
{
+ $indexableCallbacks = array();
+
foreach ($indexes as $name => $index) {
$indexId = sprintf('fos_elastica.index.%s', $name);
- $indexName = isset($index['index_name']) ? $index['index_name']: $name;
+ $indexName = isset($index['index_name']) ? $index['index_name'] : $name;
$indexDef = new DefinitionDecorator('fos_elastica.index_prototype');
$indexDef->replaceArgument(0, $indexName);
@@ -159,16 +166,20 @@ class FOSElasticaExtension extends Extension
$this->loadIndexFinder($container, $name, $reference);
}
- $this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name]);
+ $this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name], $indexableCallbacks);
}
+
+ $indexable = $container->getDefinition('fos_elastica.indexable');
+ $indexable->replaceArgument(0, $indexableCallbacks);
}
/**
* Loads the configured index finders.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
- * @param string $name The index name
- * @param Reference $index Reference to the related index
+ * @param string $name The index name
+ * @param Reference $index Reference to the related index
+ *
* @return string
*/
private function loadIndexFinder(ContainerBuilder $container, $name, Reference $index)
@@ -191,14 +202,13 @@ class FOSElasticaExtension extends Extension
/**
* Loads the configured types.
*
- * @param array $types
+ * @param array $types
* @param ContainerBuilder $container
- * @param array $indexConfig
+ * @param array $indexConfig
+ * @param array $indexableCallbacks
*/
- private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig)
+ private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig, array &$indexableCallbacks)
{
- $indexableCallbacks = array();
-
foreach ($types as $name => $type) {
$indexName = $indexConfig['name'];
@@ -216,9 +226,7 @@ class FOSElasticaExtension extends Extension
foreach (array(
'dynamic_templates',
- 'index_analyzer',
'properties',
- 'search_analyzer',
'_all',
'_boost',
'_id',
@@ -235,7 +243,12 @@ class FOSElasticaExtension extends Extension
foreach (array(
'persistence',
- 'serializer'
+ 'serializer',
+ 'index_analyzer',
+ 'search_analyzer',
+ 'date_detection',
+ 'dynamic_date_formats',
+ 'numeric_detection',
) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] :
@@ -269,19 +282,16 @@ class FOSElasticaExtension extends Extension
$container->setDefinition($typeSerializerId, $typeSerializerDef);
}
}
-
- $indexable = $container->getDefinition('fos_elastica.indexable');
- $indexable->replaceArgument(0, $indexableCallbacks);
}
/**
- * Loads the optional provider and finder for a type
+ * Loads the optional provider and finder for a type.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param Reference $typeRef
- * @param string $indexName
- * @param string $typeName
+ * @param Reference $typeRef
+ * @param string $indexName
+ * @param string $typeName
*/
private function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Reference $typeRef, $indexName, $typeName)
{
@@ -305,10 +315,11 @@ class FOSElasticaExtension extends Extension
/**
* Creates and loads an ElasticaToModelTransformer.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param string $indexName
- * @param string $typeName
+ * @param string $indexName
+ * @param string $typeName
+ *
* @return string
*/
private function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
@@ -340,10 +351,11 @@ class FOSElasticaExtension extends Extension
/**
* Creates and loads a ModelToElasticaTransformer for an index/type.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param string $indexName
- * @param string $typeName
+ * @param string $indexName
+ * @param string $typeName
+ *
* @return string
*/
private function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
@@ -359,7 +371,7 @@ class FOSElasticaExtension extends Extension
$serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId);
$serviceDef->replaceArgument(0, array(
- 'identifier' => $typeConfig['identifier']
+ 'identifier' => $typeConfig['identifier'],
));
$container->setDefinition($serviceId, $serviceDef);
@@ -369,12 +381,13 @@ class FOSElasticaExtension extends Extension
/**
* Creates and loads an object persister for a type.
*
- * @param array $typeConfig
- * @param Reference $typeRef
+ * @param array $typeConfig
+ * @param Reference $typeRef
* @param ContainerBuilder $container
- * @param string $indexName
- * @param string $typeName
- * @param string $transformerId
+ * @param string $indexName
+ * @param string $typeName
+ * @param string $transformerId
+ *
* @return string
*/
private function loadObjectPersister(array $typeConfig, Reference $typeRef, ContainerBuilder $container, $indexName, $typeName, $transformerId)
@@ -391,7 +404,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);
@@ -408,11 +426,12 @@ class FOSElasticaExtension extends Extension
/**
* Loads a provider for a type.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param string $objectPersisterId
- * @param string $indexName
- * @param string $typeName
+ * @param string $objectPersisterId
+ * @param string $indexName
+ * @param string $typeName
+ *
* @return string
*/
private function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
@@ -425,7 +444,7 @@ class FOSElasticaExtension extends Extension
* index and type names were "prototype" and a driver, respectively.
*/
$providerId = sprintf('fos_elastica.provider.%s.%s', $indexName, $typeName);
- $providerDef = new DefinitionDecorator('fos_elastica.provider.prototype.' . $typeConfig['driver']);
+ $providerDef = new DefinitionDecorator('fos_elastica.provider.prototype.'.$typeConfig['driver']);
$providerDef->addTag('fos_elastica.provider', array('index' => $indexName, 'type' => $typeName));
$providerDef->replaceArgument(0, new Reference($objectPersisterId));
$providerDef->replaceArgument(2, $typeConfig['model']);
@@ -442,11 +461,12 @@ class FOSElasticaExtension extends Extension
/**
* Loads doctrine listeners to handle indexing of new or updated objects.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param string $objectPersisterId
- * @param string $indexName
- * @param string $typeName
+ * @param string $objectPersisterId
+ * @param string $indexName
+ * @param string $typeName
+ *
* @return string
*/
private function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
@@ -462,19 +482,30 @@ class FOSElasticaExtension extends Extension
$listenerId = sprintf('fos_elastica.listener.%s.%s', $indexName, $typeName);
$listenerDef = new DefinitionDecorator($abstractListenerId);
$listenerDef->replaceArgument(0, new Reference($objectPersisterId));
- $listenerDef->replaceArgument(1, $this->getDoctrineEvents($typeConfig));
- $listenerDef->replaceArgument(3, array(
+ $listenerDef->replaceArgument(2, array(
'identifier' => $typeConfig['identifier'],
'indexName' => $indexName,
'typeName' => $typeName,
));
- if ($typeConfig['listener']['logger']) {
- $listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
+ $listenerDef->replaceArgument(3, $typeConfig['listener']['logger'] ?
+ new Reference($typeConfig['listener']['logger']) :
+ null
+ );
+
+ $tagName = null;
+ switch ($typeConfig['driver']) {
+ case 'orm':
+ $tagName = 'doctrine.event_listener';
+ break;
+ case 'mongodb':
+ $tagName = 'doctrine_mongodb.odm.event_listener';
+ break;
}
- switch ($typeConfig['driver']) {
- case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
- case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
+ if (null !== $tagName) {
+ foreach ($this->getDoctrineEvents($typeConfig) as $event) {
+ $listenerDef->addTag($tagName, array('event' => $event));
+ }
}
$container->setDefinition($listenerId, $listenerDef);
@@ -483,7 +514,7 @@ class FOSElasticaExtension extends Extension
}
/**
- * Map Elastica to Doctrine events for the current driver
+ * Map Elastica to Doctrine events for the current driver.
*/
private function getDoctrineEvents(array $typeConfig)
{
@@ -496,7 +527,6 @@ class FOSElasticaExtension extends Extension
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
- break;
}
$events = array();
@@ -504,7 +534,7 @@ class FOSElasticaExtension extends Extension
'insert' => array(constant($eventsClass.'::postPersist')),
'update' => array(constant($eventsClass.'::postUpdate')),
'delete' => array(constant($eventsClass.'::preRemove')),
- 'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush'))
+ 'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush')),
);
foreach ($eventMapping as $event => $doctrineEvents) {
@@ -519,12 +549,13 @@ class FOSElasticaExtension extends Extension
/**
* Loads a Type specific Finder.
*
- * @param array $typeConfig
+ * @param array $typeConfig
* @param ContainerBuilder $container
- * @param $elasticaToModelId
- * @param Reference $typeRef
- * @param string $indexName
- * @param string $typeName
+ * @param string $elasticaToModelId
+ * @param Reference $typeRef
+ * @param string $indexName
+ * @param string $typeName
+ *
* @return string
*/
private function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, Reference $typeRef, $indexName, $typeName)
@@ -551,7 +582,7 @@ class FOSElasticaExtension extends Extension
}
/**
- * Loads the index manager
+ * Loads the index manager.
*
* @param ContainerBuilder $container
**/
@@ -569,7 +600,7 @@ class FOSElasticaExtension extends Extension
* Makes sure a specific driver has been loaded.
*
* @param ContainerBuilder $container
- * @param string $driver
+ * @param string $driver
*/
private function loadDriver(ContainerBuilder $container, $driver)
{
@@ -585,7 +616,7 @@ class FOSElasticaExtension extends Extension
/**
* Loads and configures the serializer prototype.
*
- * @param array $config
+ * @param array $config
* @param ContainerBuilder $container
*/
private function loadSerializer($config, ContainerBuilder $container)
@@ -604,7 +635,7 @@ class FOSElasticaExtension extends Extension
/**
* Creates a default manager alias for defined default manager or the first loaded driver.
*
- * @param string $defaultManager
+ * @param string $defaultManager
* @param ContainerBuilder $container
*/
private function createDefaultManagerAlias($defaultManager, ContainerBuilder $container)
@@ -628,7 +659,9 @@ class FOSElasticaExtension extends Extension
* Returns a reference to a client given its configured name.
*
* @param string $clientName
+ *
* @return Reference
+ *
* @throws \InvalidArgumentException
*/
private function getClient($clientName)
diff --git a/Doctrine/AbstractElasticaToModelTransformer.php b/Doctrine/AbstractElasticaToModelTransformer.php
index 147067d..0263f42 100755
--- a/Doctrine/AbstractElasticaToModelTransformer.php
+++ b/Doctrine/AbstractElasticaToModelTransformer.php
@@ -2,32 +2,34 @@
namespace FOS\ElasticaBundle\Doctrine;
+use Doctrine\Common\Persistence\ManagerRegistry;
use FOS\ElasticaBundle\HybridResult;
-use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
+use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer as BaseTransformer;
use FOS\ElasticaBundle\Transformer\HighlightableModelInterface;
-use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
- * elastica documents ids and doctrine object ids
+ * elastica documents ids and doctrine object ids.
*/
-abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
+abstract class AbstractElasticaToModelTransformer extends BaseTransformer
{
/**
- * Manager registry
+ * Manager registry.
+ *
+ * @var ManagerRegistry
*/
protected $registry = null;
/**
- * Class of the model to map to the elastica documents
+ * Class of the model to map to the elastica documents.
*
* @var string
*/
protected $objectClass = null;
/**
- * Optional parameters
+ * Optional parameters.
*
* @var array
*/
@@ -39,20 +41,13 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
);
/**
- * PropertyAccessor instance
+ * Instantiates a new Mapper.
*
- * @var PropertyAccessorInterface
- */
- protected $propertyAccessor;
-
- /**
- * Instantiates a new Mapper
- *
- * @param object $registry
+ * @param ManagerRegistry $registry
* @param string $objectClass
- * @param array $options
+ * @param array $options
*/
- public function __construct($registry, $objectClass, array $options = array())
+ public function __construct(ManagerRegistry $registry, $objectClass, array $options = array())
{
$this->registry = $registry;
$this->objectClass = $objectClass;
@@ -69,22 +64,14 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
return $this->objectClass;
}
- /**
- * Set the PropertyAccessor
- *
- * @param PropertyAccessorInterface $propertyAccessor
- */
- public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
- {
- $this->propertyAccessor = $propertyAccessor;
- }
-
/**
* Transforms an array of elastica objects into an array of
- * model objects fetched from the doctrine repository
+ * model objects fetched from the doctrine repository.
*
* @param array $elasticaObjects of elastica objects
+ *
* @throws \RuntimeException
+ *
* @return array
**/
public function transform(array $elasticaObjects)
@@ -109,29 +96,31 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
// sort objects in the order of ids
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
- $propertyAccessor = $this->propertyAccessor;
- usort($objects, function($a, $b) use ($idPos, $identifier, $propertyAccessor)
- {
- return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
- });
+ usort($objects, $this->getSortingClosure($idPos, $identifier));
return $objects;
}
public function hybridTransform(array $elasticaObjects)
{
+ $indexedElasticaResults = array();
+ foreach ($elasticaObjects as $elasticaObject) {
+ $indexedElasticaResults[$elasticaObject->getId()] = $elasticaObject;
+ }
+
$objects = $this->transform($elasticaObjects);
$result = array();
- for ($i = 0; $i < count($elasticaObjects); $i++) {
- $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
+ foreach ($objects as $object) {
+ $id = $this->propertyAccessor->getValue($object, $this->options['identifier']);
+ $result[] = new HybridResult($indexedElasticaResults[$id], $object);
}
return $result;
}
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
public function getIdentifierField()
{
@@ -139,11 +128,12 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
}
/**
- * Fetches objects by theses identifier values
+ * Fetches objects by theses identifier values.
+ *
+ * @param array $identifierValues ids values
+ * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
- * @param array $identifierValues ids values
- * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
- protected abstract function findByIdentifiers(array $identifierValues, $hydrate);
+ abstract protected function findByIdentifiers(array $identifierValues, $hydrate);
}
diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php
index a662fd4..ec198a8 100644
--- a/Doctrine/AbstractProvider.php
+++ b/Doctrine/AbstractProvider.php
@@ -7,125 +7,61 @@ use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
use FOS\ElasticaBundle\Provider\IndexableInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class AbstractProvider extends BaseAbstractProvider
{
+ /**
+ * @var SliceFetcherInterface
+ */
+ private $sliceFetcher;
+
+ /**
+ * @var ManagerRegistry
+ */
protected $managerRegistry;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param IndexableInterface $indexable
- * @param string $objectClass
- * @param array $options
- * @param ManagerRegistry $managerRegistry
+ * @param IndexableInterface $indexable
+ * @param string $objectClass
+ * @param array $baseOptions
+ * @param ManagerRegistry $managerRegistry
+ * @param SliceFetcherInterface $sliceFetcher
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
- array $options,
- ManagerRegistry $managerRegistry
+ array $baseOptions,
+ ManagerRegistry $managerRegistry,
+ SliceFetcherInterface $sliceFetcher = null
) {
- parent::__construct($objectPersister, $indexable, $objectClass, array_merge(array(
- 'clear_object_manager' => true,
- 'debug_logging' => false,
- 'ignore_errors' => false,
- 'query_builder_method' => 'createQueryBuilder',
- ), $options));
+ parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions);
$this->managerRegistry = $managerRegistry;
- }
-
- /**
- * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
- */
- public function populate(\Closure $loggerClosure = null, array $options = array())
- {
- if (!$this->options['debug_logging']) {
- $logger = $this->disableLogging();
- }
-
- $queryBuilder = $this->createQueryBuilder();
- $nbObjects = $this->countObjects($queryBuilder);
- $offset = isset($options['offset']) ? intval($options['offset']) : 0;
- $sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
- $batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
- $ignoreErrors = isset($options['ignore-errors']) ? $options['ignore-errors'] : $this->options['ignore_errors'];
- $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('Entire batch was filtered away, skipping...');
- }
-
- continue;
- }
-
- if (!$ignoreErrors) {
- $this->objectPersister->insertMany($objects);
- } else {
- try {
- $this->objectPersister->insertMany($objects);
- } catch(BulkResponseException $e) {
- if ($loggerClosure) {
- $loggerClosure(sprintf('%s',$e->getMessage()));
- }
- }
- }
-
- if ($this->options['clear_object_manager']) {
- $manager->clear();
- }
-
- 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()));
- }
- }
-
- if (!$this->options['debug_logging']) {
- $this->enableLogging($logger);
- }
+ $this->sliceFetcher = $sliceFetcher;
}
/**
* Counts objects that would be indexed using the query builder.
*
* @param object $queryBuilder
+ *
* @return integer
*/
- protected abstract function countObjects($queryBuilder);
+ abstract protected function countObjects($queryBuilder);
/**
- * Disables logging and returns the logger that was previously set.
+ * Creates the query builder, which will be used to fetch objects to index.
*
- * @return mixed
- */
- protected abstract function disableLogging();
-
- /**
- * Reenables the logger with the previously returned logger from disableLogging();
+ * @param string $method
*
- * @param mixed $logger
- * @return mixed
+ * @return object
*/
- protected abstract function enableLogging($logger);
+ abstract protected function createQueryBuilder($method);
/**
* Fetches a slice of objects using the query builder.
@@ -133,14 +69,102 @@ abstract class AbstractProvider extends BaseAbstractProvider
* @param object $queryBuilder
* @param integer $limit
* @param integer $offset
+ *
* @return array
*/
- protected abstract function fetchSlice($queryBuilder, $limit, $offset);
+ abstract protected function fetchSlice($queryBuilder, $limit, $offset);
/**
- * Creates the query builder, which will be used to fetch objects to index.
- *
- * @return object
+ * {@inheritDoc}
*/
- protected abstract function createQueryBuilder();
+ protected function doPopulate($options, \Closure $loggerClosure = null)
+ {
+ $manager = $this->managerRegistry->getManagerForClass($this->objectClass);
+
+ $queryBuilder = $this->createQueryBuilder($options['query_builder_method']);
+ $nbObjects = $this->countObjects($queryBuilder);
+ $offset = $options['offset'];
+
+ $objects = array();
+ for (; $offset < $nbObjects; $offset += $options['batch_size']) {
+ try {
+ $objects = $this->getSlice($queryBuilder, $options['batch_size'], $offset, $objects);
+ $objects = $this->filterObjects($options, $objects);
+
+ if (!empty($objects)) {
+ $this->objectPersister->insertMany($objects);
+ }
+ } catch (BulkResponseException $e) {
+ if (!$options['ignore_errors']) {
+ throw $e;
+ }
+
+ if (null !== $loggerClosure) {
+ $loggerClosure(
+ $options['batch_size'],
+ $nbObjects,
+ sprintf('%s', $e->getMessage())
+ );
+ }
+ }
+
+ if ($options['clear_object_manager']) {
+ $manager->clear();
+ }
+
+ usleep($options['sleep']);
+
+ if (null !== $loggerClosure) {
+ $loggerClosure($options['batch_size'], $nbObjects);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function configureOptions()
+ {
+ parent::configureOptions();
+
+ $this->resolver->setDefaults(array(
+ 'clear_object_manager' => true,
+ 'debug_logging' => false,
+ 'ignore_errors' => false,
+ 'offset' => 0,
+ 'query_builder_method' => 'createQueryBuilder',
+ 'sleep' => 0
+ ));
+ }
+
+ /**
+ * If this Provider has a SliceFetcher defined, we use it instead of falling back to
+ * the fetchSlice methods defined in the ORM/MongoDB subclasses.
+ *
+ * @param $queryBuilder
+ * @param int $limit
+ * @param int $offset
+ * @param array $lastSlice
+ *
+ * @return array
+ */
+ private function getSlice($queryBuilder, $limit, $offset, $lastSlice)
+ {
+ if (!$this->sliceFetcher) {
+ return $this->fetchSlice($queryBuilder, $limit, $offset);
+ }
+
+ $manager = $this->managerRegistry->getManagerForClass($this->objectClass);
+ $identifierFieldNames = $manager
+ ->getClassMetadata($this->objectClass)
+ ->getIdentifierFieldNames();
+
+ return $this->sliceFetcher->fetch(
+ $queryBuilder,
+ $limit,
+ $offset,
+ $lastSlice,
+ $identifierFieldNames
+ );
+ }
}
diff --git a/Doctrine/Listener.php b/Doctrine/Listener.php
index 73a271d..a1d3585 100644
--- a/Doctrine/Listener.php
+++ b/Doctrine/Listener.php
@@ -2,11 +2,11 @@
namespace FOS\ElasticaBundle\Doctrine;
-use Doctrine\Common\EventArgs;
-use Doctrine\Common\EventSubscriber;
-use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
+use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Persister\ObjectPersister;
+use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\IndexableInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -14,49 +14,52 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
* Automatically update ElasticSearch based on changes to the Doctrine source
* data. One listener is generated for each Doctrine entity / ElasticSearch type.
*/
-class Listener implements EventSubscriber
+class Listener
{
/**
- * Object persister
+ * Object persister.
*
- * @var ObjectPersister
+ * @var ObjectPersisterInterface
*/
protected $objectPersister;
/**
- * List of subscribed events
+ * Configuration for the listener.
*
* @var array
*/
- protected $events;
-
- /**
- * Configuration for the listener
- *
- * @var string
- */
private $config;
/**
- * Objects scheduled for insertion and replacement
+ * Objects scheduled for insertion.
+ *
+ * @var array
*/
public $scheduledForInsertion = array();
+
+ /**
+ * Objects scheduled to be updated or removed.
+ *
+ * @var array
+ */
public $scheduledForUpdate = array();
/**
- * IDs of objects scheduled for removal
+ * IDs of objects scheduled for removal.
+ *
+ * @var array
*/
public $scheduledForDeletion = array();
/**
- * PropertyAccessor instance
+ * PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
- * @var \FOS\ElasticaBundle\Provider\IndexableInterface
+ * @var IndexableInterface
*/
private $indexable;
@@ -64,71 +67,50 @@ class Listener implements EventSubscriber
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param array $events
- * @param IndexableInterface $indexable
- * @param array $config
- * @param null $logger
+ * @param IndexableInterface $indexable
+ * @param array $config
+ * @param LoggerInterface $logger
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
- array $events,
IndexableInterface $indexable,
array $config = array(),
- $logger = null
+ LoggerInterface $logger = null
) {
$this->config = array_merge(array(
'identifier' => 'id',
), $config);
- $this->events = $events;
$this->indexable = $indexable;
$this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
- if ($logger) {
+ if ($logger && $this->objectPersister instanceof ObjectPersister) {
$this->objectPersister->setLogger($logger);
}
}
/**
- * @see Doctrine\Common\EventSubscriber::getSubscribedEvents()
- */
- public function getSubscribedEvents()
- {
- return $this->events;
- }
-
- /**
- * Provides unified method for retrieving a doctrine object from an EventArgs instance
+ * Looks for new objects that should be indexed.
*
- * @param EventArgs $eventArgs
- * @return object Entity | Document
- * @throws \RuntimeException if no valid getter is found.
+ * @param LifecycleEventArgs $eventArgs
*/
- private function getDoctrineObject(EventArgs $eventArgs)
+ public function postPersist(LifecycleEventArgs $eventArgs)
{
- if (method_exists($eventArgs, 'getObject')) {
- return $eventArgs->getObject();
- } elseif (method_exists($eventArgs, 'getEntity')) {
- return $eventArgs->getEntity();
- } elseif (method_exists($eventArgs, 'getDocument')) {
- return $eventArgs->getDocument();
- }
-
- throw new \RuntimeException('Unable to retrieve object from EventArgs.');
- }
-
- public function postPersist(EventArgs $eventArgs)
- {
- $entity = $this->getDoctrineObject($eventArgs);
+ $entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) {
$this->scheduledForInsertion[] = $entity;
}
}
- public function postUpdate(EventArgs $eventArgs)
+ /**
+ * Looks for objects being updated that should be indexed or removed from the index.
+ *
+ * @param LifecycleEventArgs $eventArgs
+ */
+ public function postUpdate(LifecycleEventArgs $eventArgs)
{
- $entity = $this->getDoctrineObject($eventArgs);
+ $entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
@@ -142,11 +124,13 @@ class Listener implements EventSubscriber
/**
* Delete objects preRemove instead of postRemove so that we have access to the id. Because this is called
- * preRemove, first check that the entity is managed by Doctrine
+ * preRemove, first check that the entity is managed by Doctrine.
+ *
+ * @param LifecycleEventArgs $eventArgs
*/
- public function preRemove(EventArgs $eventArgs)
+ public function preRemove(LifecycleEventArgs $eventArgs)
{
- $entity = $this->getDoctrineObject($eventArgs);
+ $entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
@@ -155,7 +139,7 @@ class Listener implements EventSubscriber
/**
* Persist scheduled objects to ElasticSearch
- * After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls
+ * After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
*/
private function persistScheduled()
{
@@ -174,29 +158,36 @@ class Listener implements EventSubscriber
}
/**
- * Iterate through scheduled actions before flushing to emulate 2.x behavior. Note that the ElasticSearch index
- * will fall out of sync with the source data in the event of a crash during flush.
+ * Iterate through scheduled actions before flushing to emulate 2.x behavior.
+ * Note that the ElasticSearch index will fall out of sync with the source
+ * data in the event of a crash during flush.
+ *
+ * This method is only called in legacy configurations of the listener.
+ *
+ * @deprecated This method should only be called in applications that depend
+ * on the behaviour that entities are indexed regardless of if a
+ * flush is successful.
*/
- public function preFlush(EventArgs $eventArgs)
+ public function preFlush()
{
$this->persistScheduled();
}
/**
- * Iterating through scheduled actions *after* flushing ensures that the ElasticSearch index will be affected
- * only if the query is successful
+ * Iterating through scheduled actions *after* flushing ensures that the
+ * ElasticSearch index will be affected only if the query is successful.
*/
- public function postFlush(EventArgs $eventArgs)
+ public function postFlush()
{
$this->persistScheduled();
}
/**
* Record the specified identifier to delete. Do not need to entire object.
- * @param mixed $object
- * @return mixed
+ *
+ * @param object $object
*/
- protected function scheduleForDeletion($object)
+ private function scheduleForDeletion($object)
{
if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
$this->scheduledForDeletion[] = $identifierValue;
@@ -207,6 +198,7 @@ class Listener implements EventSubscriber
* Checks if the object is indexable or not.
*
* @param object $object
+ *
* @return bool
*/
private function isObjectIndexable($object)
diff --git a/Doctrine/MongoDB/ElasticaToModelTransformer.php b/Doctrine/MongoDB/ElasticaToModelTransformer.php
index 855a093..23a8292 100644
--- a/Doctrine/MongoDB/ElasticaToModelTransformer.php
+++ b/Doctrine/MongoDB/ElasticaToModelTransformer.php
@@ -7,21 +7,23 @@ use FOS\ElasticaBundle\Doctrine\AbstractElasticaToModelTransformer;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
- * elastica documents ids and doctrine object ids
+ * elastica documents ids and doctrine object ids.
*/
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
/**
- * Fetch objects for theses identifier values
+ * Fetch objects for theses identifier values.
+ *
+ * @param array $identifierValues ids values
+ * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
- * @param array $identifierValues ids values
- * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
{
return $this->registry
->getManagerForClass($this->objectClass)
+ ->getRepository($this->objectClass)
->{$this->options['query_builder_method']}($this->objectClass)
->field($this->options['identifier'])->in($identifierValues)
->hydrate($hydrate)
diff --git a/Doctrine/MongoDB/Provider.php b/Doctrine/MongoDB/Provider.php
index 9e1c5dd..e4b08c5 100644
--- a/Doctrine/MongoDB/Provider.php
+++ b/Doctrine/MongoDB/Provider.php
@@ -27,9 +27,10 @@ class Provider extends AbstractProvider
}
/**
- * Reenables the logger with the previously returned logger from disableLogging();
+ * Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
+ *
* @return mixed
*/
protected function enableLogging($logger)
@@ -43,7 +44,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
+ * {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@@ -57,7 +58,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
+ * {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@@ -66,21 +67,21 @@ class Provider extends AbstractProvider
}
return $queryBuilder
- ->limit($limit)
->skip($offset)
+ ->limit($limit)
->getQuery()
->execute()
->toArray();
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
+ * {@inheritDoc}
*/
- protected function createQueryBuilder()
+ protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
- ->{$this->options['query_builder_method']}();
+ ->{$method}();
}
}
diff --git a/Doctrine/MongoDB/SliceFetcher.php b/Doctrine/MongoDB/SliceFetcher.php
new file mode 100644
index 0000000..4723da6
--- /dev/null
+++ b/Doctrine/MongoDB/SliceFetcher.php
@@ -0,0 +1,44 @@
+
+ */
+class SliceFetcher implements SliceFetcherInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames)
+ {
+ if (!$queryBuilder instanceof Builder) {
+ throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ODM\MongoDB\Query\Builder');
+ }
+
+ $lastObject = array_pop($previousSlice);
+
+ if ($lastObject) {
+ $queryBuilder
+ ->field('_id')->gt($lastObject->getId())
+ ->skip(0)
+ ;
+ } else {
+ $queryBuilder->skip($offset);
+ }
+
+ return $queryBuilder
+ ->limit($limit)
+ ->sort(array('_id' => 'asc'))
+ ->getQuery()
+ ->execute()
+ ->toArray()
+ ;
+ }
+}
diff --git a/Doctrine/ORM/ElasticaToModelTransformer.php b/Doctrine/ORM/ElasticaToModelTransformer.php
index a57a84c..21d8640 100644
--- a/Doctrine/ORM/ElasticaToModelTransformer.php
+++ b/Doctrine/ORM/ElasticaToModelTransformer.php
@@ -8,17 +8,18 @@ use Doctrine\ORM\Query;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
- * elastica documents ids and doctrine object ids
+ * elastica documents ids and doctrine object ids.
*/
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
const ENTITY_ALIAS = 'o';
/**
- * Fetch objects for theses identifier values
+ * Fetch objects for theses identifier values.
+ *
+ * @param array $identifierValues ids values
+ * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
- * @param array $identifierValues ids values
- * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
@@ -36,7 +37,7 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
}
/**
- * Retrieves a query builder to be used for querying by identifiers
+ * Retrieves a query builder to be used for querying by identifiers.
*
* @return \Doctrine\ORM\QueryBuilder
*/
diff --git a/Doctrine/ORM/Provider.php b/Doctrine/ORM/Provider.php
index dfd6700..85b5279 100644
--- a/Doctrine/ORM/Provider.php
+++ b/Doctrine/ORM/Provider.php
@@ -3,7 +3,6 @@
namespace FOS\ElasticaBundle\Doctrine\ORM;
use Doctrine\ORM\QueryBuilder;
-use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Doctrine\AbstractProvider;
use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException;
@@ -30,9 +29,10 @@ class Provider extends AbstractProvider
}
/**
- * Reenables the logger with the previously returned logger from disableLogging();
+ * Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
+ *
* @return mixed
*/
protected function enableLogging($logger)
@@ -46,7 +46,7 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
+ * {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@@ -69,7 +69,9 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
+ * This method should remain in sync with SliceFetcher::fetch until it is deprecated and removed.
+ *
+ * {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@@ -77,8 +79,8 @@ class Provider extends AbstractProvider
throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
- /**
- * An orderBy DQL part is required to avoid feching the same row twice.
+ /*
+ * An orderBy DQL part is required to avoid fetching the same row twice.
* @see http://stackoverflow.com/questions/6314879/does-limit-offset-length-require-order-by-for-pagination
* @see http://www.postgresql.org/docs/current/static/queries-limit.html
* @see http://www.sqlite.org/lang_select.html#orderby
@@ -103,14 +105,14 @@ class Provider extends AbstractProvider
}
/**
- * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
+ * {@inheritDoc}
*/
- protected function createQueryBuilder()
+ protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
// ORM query builders require an alias argument
- ->{$this->options['query_builder_method']}(static::ENTITY_ALIAS);
+ ->{$method}(static::ENTITY_ALIAS);
}
}
diff --git a/Doctrine/ORM/SliceFetcher.php b/Doctrine/ORM/SliceFetcher.php
new file mode 100644
index 0000000..ac6c816
--- /dev/null
+++ b/Doctrine/ORM/SliceFetcher.php
@@ -0,0 +1,50 @@
+
+ */
+class SliceFetcher implements SliceFetcherInterface
+{
+ /**
+ * This method should remain in sync with Provider::fetchSlice until that method is deprecated and
+ * removed.
+ *
+ * {@inheritdoc}
+ */
+ public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames)
+ {
+ if (!$queryBuilder instanceof QueryBuilder) {
+ throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
+ }
+
+ /*
+ * An orderBy DQL part is required to avoid feching the same row twice.
+ * @see http://stackoverflow.com/questions/6314879/does-limit-offset-length-require-order-by-for-pagination
+ * @see http://www.postgresql.org/docs/current/static/queries-limit.html
+ * @see http://www.sqlite.org/lang_select.html#orderby
+ */
+ $orderBy = $queryBuilder->getDQLPart('orderBy');
+ if (empty($orderBy)) {
+ $rootAliases = $queryBuilder->getRootAliases();
+
+ foreach ($identifierFieldNames as $fieldName) {
+ $queryBuilder->addOrderBy($rootAliases[0].'.'.$fieldName);
+ }
+ }
+
+ return $queryBuilder
+ ->setFirstResult($offset)
+ ->setMaxResults($limit)
+ ->getQuery()
+ ->getResult()
+ ;
+ }
+}
diff --git a/Doctrine/RepositoryManager.php b/Doctrine/RepositoryManager.php
index f8867eb..0d20f64 100644
--- a/Doctrine/RepositoryManager.php
+++ b/Doctrine/RepositoryManager.php
@@ -25,7 +25,7 @@ class RepositoryManager extends BaseManager
}
/**
- * Return repository for entity
+ * Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
@@ -35,7 +35,7 @@ class RepositoryManager extends BaseManager
$realEntityName = $entityName;
if (strpos($entityName, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $entityName);
- $realEntityName = $this->managerRegistry->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName;
+ $realEntityName = $this->managerRegistry->getAliasNamespace($namespaceAlias).'\\'.$simpleClassName;
}
return parent::getRepository($realEntityName);
diff --git a/Doctrine/SliceFetcherInterface.php b/Doctrine/SliceFetcherInterface.php
new file mode 100644
index 0000000..a028abf
--- /dev/null
+++ b/Doctrine/SliceFetcherInterface.php
@@ -0,0 +1,24 @@
+
+ */
+interface SliceFetcherInterface
+{
+ /**
+ * Fetches a slice of objects using the query builder.
+ *
+ * @param object $queryBuilder
+ * @param integer $limit
+ * @param integer $offset
+ * @param array $previousSlice
+ * @param array $identifierFieldNames
+ *
+ * @return array
+ */
+ public function fetch($queryBuilder, $limit, $offset, array $previousSlice, array $identifierFieldNames);
+}
diff --git a/Elastica/Client.php b/Elastica/Client.php
index 1131ba5..c244eb4 100644
--- a/Elastica/Client.php
+++ b/Elastica/Client.php
@@ -23,14 +23,19 @@ class Client extends BaseClient
private $indexCache = array();
/**
- * Symfony's debugging Stopwatch
+ * Symfony's debugging Stopwatch.
*
* @var Stopwatch|null
*/
private $stopwatch;
/**
- * {@inheritdoc}
+ * @param string $path
+ * @param string $method
+ * @param array $data
+ * @param array $query
+ *
+ * @return \Elastica\Response
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
@@ -41,20 +46,7 @@ class Client extends BaseClient
$start = microtime(true);
$response = parent::request($path, $method, $data, $query);
- if ($this->_logger and $this->_logger instanceof ElasticaLogger) {
- $time = microtime(true) - $start;
-
- $connection = $this->getLastRequest()->getConnection();
-
- $connection_array = array(
- 'host' => $connection->getHost(),
- 'port' => $connection->getPort(),
- 'transport' => $connection->getTransport(),
- 'headers' => $connection->hasConfig('headers') ? $connection->getConfig('headers') : array(),
- );
-
- $this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
- }
+ $this->logQuery($path, $method, $data, $query, $start);
if ($this->stopwatch) {
$this->stopwatch->stop('es_request');
@@ -81,4 +73,32 @@ class Client extends BaseClient
{
$this->stopwatch = $stopwatch;
}
+
+ /**
+ * Log the query if we have an instance of ElasticaLogger.
+ *
+ * @param string $path
+ * @param string $method
+ * @param array $data
+ * @param array $query
+ * @param int $start
+ */
+ private function logQuery($path, $method, $data, array $query, $start)
+ {
+ if (!$this->_logger or !$this->_logger instanceof ElasticaLogger) {
+ return;
+ }
+
+ $time = microtime(true) - $start;
+ $connection = $this->getLastRequest()->getConnection();
+
+ $connection_array = array(
+ 'host' => $connection->getHost(),
+ 'port' => $connection->getPort(),
+ 'transport' => $connection->getTransport(),
+ 'headers' => $connection->hasConfig('headers') ? $connection->getConfig('headers') : array(),
+ );
+
+ $this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
+ }
}
diff --git a/Elastica/Index.php b/Elastica/Index.php
index bf37c51..ad3bd4d 100644
--- a/Elastica/Index.php
+++ b/Elastica/Index.php
@@ -3,7 +3,6 @@
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Index as BaseIndex;
-use Elastica\Type;
/**
* Overridden Elastica Index class that provides dynamic index name changes.
@@ -32,6 +31,9 @@ class Index extends BaseIndex
return $this->originalName ?: $this->_name;
}
+ /**
+ * @param string $type
+ */
public function getType($type)
{
if (isset($this->typeCache[$type])) {
@@ -42,14 +44,12 @@ class Index extends BaseIndex
}
/**
- * Reassign index name
+ * Reassign index name.
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
- *
- * @return void
*/
public function overrideName($name)
{
diff --git a/Event/IndexEvent.php b/Event/IndexEvent.php
new file mode 100644
index 0000000..ed71d78
--- /dev/null
+++ b/Event/IndexEvent.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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 IndexEvent extends Event
+{
+ /**
+ * @var string
+ */
+ private $index;
+
+ /**
+ * @param string $index
+ */
+ public function __construct($index)
+ {
+ $this->index = $index;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+}
diff --git a/Event/IndexPopulateEvent.php b/Event/IndexPopulateEvent.php
new file mode 100644
index 0000000..b35105a
--- /dev/null
+++ b/Event/IndexPopulateEvent.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Event;
+
+/**
+ * Index Populate Event.
+ *
+ * @author Oleg Andreyev
+ */
+class IndexPopulateEvent extends IndexEvent
+{
+ const PRE_INDEX_POPULATE = 'elastica.index.index_pre_populate';
+ const POST_INDEX_POPULATE = 'elastica.index.index_post_populate';
+
+ /**
+ * @var bool
+ */
+ private $reset;
+
+ /**
+ * @var array
+ */
+ private $options;
+
+ /**
+ * @param string $index
+ * @param boolean $reset
+ * @param array $options
+ */
+ public function __construct($index, $reset, $options)
+ {
+ parent::__construct($index);
+
+ $this->reset = $reset;
+ $this->options = $options;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isReset()
+ {
+ return $this->reset;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @param boolean $reset
+ */
+ public function setReset($reset)
+ {
+ $this->reset = $reset;
+ }
+}
diff --git a/Event/IndexResetEvent.php b/Event/IndexResetEvent.php
new file mode 100644
index 0000000..871915a
--- /dev/null
+++ b/Event/IndexResetEvent.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Event;
+
+/**
+ * Index ResetEvent.
+ *
+ * @author Oleg Andreyev
+ */
+class IndexResetEvent extends IndexEvent
+{
+ const PRE_INDEX_RESET = 'elastica.index.pre_reset';
+ const POST_INDEX_RESET = 'elastica.index.post_reset';
+
+ /**
+ * @var bool
+ */
+ private $force;
+
+ /**
+ * @var bool
+ */
+ private $populating;
+
+ /**
+ * @param string $index
+ * @param bool $populating
+ * @param bool $force
+ */
+ public function __construct($index, $populating, $force)
+ {
+ parent::__construct($index);
+
+ $this->force = $force;
+ $this->populating = $populating;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isForce()
+ {
+ return $this->force;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isPopulating()
+ {
+ return $this->populating;
+ }
+
+ /**
+ * @param boolean $force
+ */
+ public function setForce($force)
+ {
+ $this->force = $force;
+ }
+}
diff --git a/Event/TransformEvent.php b/Event/TransformEvent.php
new file mode 100644
index 0000000..4f6871f
--- /dev/null
+++ b/Event/TransformEvent.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Event;
+
+use Symfony\Component\EventDispatcher\Event;
+
+class TransformEvent extends Event
+{
+ const POST_TRANSFORM = 'fos_elastica.post_transform';
+
+ /**
+ * @var mixed
+ */
+ private $document;
+
+ /**
+ * @var array
+ */
+ private $fields;
+
+ /**
+ * @var mixed
+ */
+ private $object;
+
+ /**
+ * @param mixed $document
+ * @param array $fields
+ * @param mixed $object
+ */
+ public function __construct($document, array $fields, $object)
+ {
+ $this->document = $document;
+ $this->fields = $fields;
+ $this->object = $object;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFields()
+ {
+ return $this->fields;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * @param mixed $document
+ */
+ public function setDocument($document)
+ {
+ $this->document = $document;
+ }
+}
diff --git a/Event/TypePopulateEvent.php b/Event/TypePopulateEvent.php
new file mode 100644
index 0000000..dd744f5
--- /dev/null
+++ b/Event/TypePopulateEvent.php
@@ -0,0 +1,51 @@
+
+ *
+ * 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;
+
+/**
+ * Type Populate Event.
+ *
+ * @author Oleg Andreyev
+ */
+class TypePopulateEvent extends IndexPopulateEvent
+{
+ const PRE_TYPE_POPULATE = 'elastica.index.type_pre_populate';
+ const POST_TYPE_POPULATE = 'elastica.index.type_post_populate';
+
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @param string $index
+ * @param string $type
+ * @param bool $reset
+ * @param array $options
+ */
+ public function __construct($index, $type, $reset, $options)
+ {
+ parent::__construct($index, $reset, $options);
+
+ $this->type = $type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+}
diff --git a/Event/TypeResetEvent.php b/Event/TypeResetEvent.php
new file mode 100644
index 0000000..98fa2f4
--- /dev/null
+++ b/Event/TypeResetEvent.php
@@ -0,0 +1,49 @@
+
+ *
+ * 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;
+
+/**
+ * Type ResetEvent.
+ *
+ * @author Oleg Andreyev
+ */
+class TypeResetEvent extends IndexEvent
+{
+ const PRE_TYPE_RESET = 'elastica.index.type_pre_reset';
+ const POST_TYPE_RESET = 'elastica.index.type_post_reset';
+
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @param string $index
+ * @param string $type
+ */
+ public function __construct($index, $type)
+ {
+ parent::__construct($index);
+
+ $this->type = $type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+}
diff --git a/Exception/AliasIsIndexException.php b/Exception/AliasIsIndexException.php
new file mode 100644
index 0000000..9af6ee3
--- /dev/null
+++ b/Exception/AliasIsIndexException.php
@@ -0,0 +1,11 @@
+result;
}
-}
\ No newline at end of file
+}
diff --git a/Index/AliasProcessor.php b/Index/AliasProcessor.php
index 93877cd..d8f608d 100644
--- a/Index/AliasProcessor.php
+++ b/Index/AliasProcessor.php
@@ -11,10 +11,12 @@
namespace FOS\ElasticaBundle\Index;
+use Elastica\Client;
use Elastica\Exception\ExceptionInterface;
+use Elastica\Request;
use FOS\ElasticaBundle\Configuration\IndexConfig;
-use FOS\ElasticaBundle\Elastica\Client;
use FOS\ElasticaBundle\Elastica\Index;
+use FOS\ElasticaBundle\Exception\AliasIsIndexException;
class AliasProcessor
{
@@ -22,115 +24,174 @@ class AliasProcessor
* Sets the randomised root name for an index.
*
* @param IndexConfig $indexConfig
- * @param Index $index
+ * @param Index $index
*/
public function setRootName(IndexConfig $indexConfig, Index $index)
{
- $index->overrideName(sprintf('%s_%s', $indexConfig->getElasticSearchName(), uniqid()));
+ $index->overrideName(
+ sprintf('%s_%s',
+ $indexConfig->getElasticSearchName(),
+ date('Y-m-d-His')
+ )
+ );
}
/**
* Switches an index to become the new target for an alias. Only applies for
* indexes that are set to use aliases.
*
+ * $force will delete an index encountered where an alias is expected.
+ *
* @param IndexConfig $indexConfig
- * @param Index $index
+ * @param Index $index
+ * @param bool $force
+ *
+ * @throws AliasIsIndexException
* @throws \RuntimeException
*/
- public function switchIndexAlias(IndexConfig $indexConfig, Index $index)
+ public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false)
{
$client = $index->getClient();
$aliasName = $indexConfig->getElasticSearchName();
- $oldIndexName = false;
+ $oldIndexName = null;
$newIndexName = $index->getName();
- $aliasedIndexes = $this->getAliasedIndexes($client, $aliasName);
+ try {
+ $oldIndexName = $this->getAliasedIndex($client, $aliasName);
+ } catch (AliasIsIndexException $e) {
+ if (!$force) {
+ throw $e;
+ }
- if (count($aliasedIndexes) > 1) {
- throw new \RuntimeException(
- sprintf(
- 'Alias %s is used for multiple indexes: [%s].
- Make sure it\'s either not used or is assigned to one index only',
- $aliasName,
- join(', ', $aliasedIndexes)
- )
- );
+ $this->deleteIndex($client, $aliasName);
}
+ try {
+ $aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
+ $client->request('_aliases', 'POST', $aliasUpdateRequest);
+ } catch (ExceptionInterface $e) {
+ $this->cleanupRenameFailure($client, $newIndexName, $e);
+ }
+
+ // Delete the old index after the alias has been switched
+ if (null !== $oldIndexName) {
+ $this->deleteIndex($client, $oldIndexName);
+ }
+ }
+
+ /**
+ * Builds an ElasticSearch request to rename or create an alias.
+ *
+ * @param string|null $aliasedIndex
+ * @param string $aliasName
+ * @param string $newIndexName
+ * @return array
+ */
+ private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
+ {
$aliasUpdateRequest = array('actions' => array());
- if (count($aliasedIndexes) == 1) {
+ if (null !== $aliasedIndex) {
// if the alias is set - add an action to remove it
- $oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
- 'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
+ 'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
- 'add' => array('index' => $newIndexName, 'alias' => $aliasName)
+ 'add' => array('index' => $newIndexName, 'alias' => $aliasName),
);
- try {
- $client->request('_aliases', 'POST', $aliasUpdateRequest);
- } catch (ExceptionInterface $renameAliasException) {
- $additionalError = '';
- // if we failed to move the alias, delete the newly built index
- try {
- $index->delete();
- } catch (ExceptionInterface $deleteNewIndexException) {
- $additionalError = sprintf(
- 'Tried to delete newly built index %s, but also failed: %s',
- $newIndexName,
- $deleteNewIndexException->getMessage()
- );
- }
+ return $aliasUpdateRequest;
+ }
- throw new \RuntimeException(
- sprintf(
- 'Failed to updated index alias: %s. %s',
- $renameAliasException->getMessage(),
- $additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
- ), 0, $renameAliasException
+ /**
+ * Cleans up an index when we encounter a failure to rename the alias.
+ *
+ * @param Client $client
+ * @param string $indexName
+ * @param \Exception $renameAliasException
+ */
+ private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
+ {
+ $additionalError = '';
+ try {
+ $this->deleteIndex($client, $indexName);
+ } catch (ExceptionInterface $deleteNewIndexException) {
+ $additionalError = sprintf(
+ 'Tried to delete newly built index %s, but also failed: %s',
+ $indexName,
+ $deleteNewIndexException->getMessage()
);
}
- // Delete the old index after the alias has been switched
- if ($oldIndexName) {
- $oldIndex = new Index($client, $oldIndexName);
- try {
- $oldIndex->delete();
- } catch (ExceptionInterface $deleteOldIndexException) {
- throw new \RuntimeException(
- sprintf(
- 'Failed to delete old index %s with message: %s',
- $oldIndexName,
- $deleteOldIndexException->getMessage()
- ), 0, $deleteOldIndexException
- );
- }
+ throw new \RuntimeException(sprintf(
+ 'Failed to updated index alias: %s. %s',
+ $renameAliasException->getMessage(),
+ $additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
+ ), 0, $renameAliasException);
+ }
+
+ /**
+ * Delete an index.
+ *
+ * @param Client $client
+ * @param string $indexName Index name to delete
+ */
+ private function deleteIndex(Client $client, $indexName)
+ {
+ try {
+ $path = sprintf("%s", $indexName);
+ $client->request($path, Request::DELETE);
+ } catch (ExceptionInterface $deleteOldIndexException) {
+ throw new \RuntimeException(sprintf(
+ 'Failed to delete index %s with message: %s',
+ $indexName,
+ $deleteOldIndexException->getMessage()
+ ), 0, $deleteOldIndexException);
}
}
/**
- * Returns array of indexes which are mapped to given alias
+ * Returns the name of a single index that an alias points to or throws
+ * an exception if there is more than one.
*
+ * @param Client $client
* @param string $aliasName Alias name
- * @return array
+ *
+ * @return string|null
+ *
+ * @throws AliasIsIndexException
*/
- private function getAliasedIndexes(Client $client, $aliasName)
+ private function getAliasedIndex(Client $client, $aliasName)
{
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
+ if ($indexName === $aliasName) {
+ throw new AliasIsIndexException($indexName);
+ }
+ if (!isset($indexInfo['aliases'])) {
+ continue;
+ }
+
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
- return $aliasedIndexes;
+ if (count($aliasedIndexes) > 1) {
+ throw new \RuntimeException(sprintf(
+ 'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
+ 'either not used or is assigned to one index only',
+ $aliasName,
+ implode(', ', $aliasedIndexes)
+ ));
+ }
+
+ return array_shift($aliasedIndexes);
}
}
diff --git a/Index/IndexManager.php b/Index/IndexManager.php
index 38249a7..98ce870 100644
--- a/Index/IndexManager.php
+++ b/Index/IndexManager.php
@@ -6,6 +6,11 @@ use FOS\ElasticaBundle\Elastica\Index;
class IndexManager
{
+ /**
+ * @var Index
+ */
+ private $defaultIndex;
+
/**
* @var array
*/
@@ -22,7 +27,7 @@ class IndexManager
}
/**
- * Gets all registered indexes
+ * Gets all registered indexes.
*
* @return array
*/
@@ -32,10 +37,12 @@ class IndexManager
}
/**
- * Gets an index by its name
+ * Gets an index by its name.
*
* @param string $name Index to return, or the default index if null
+ *
* @return Index
+ *
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function getIndex($name = null)
@@ -52,7 +59,7 @@ class IndexManager
}
/**
- * Gets the default index
+ * Gets the default index.
*
* @return Index
*/
diff --git a/Index/MappingBuilder.php b/Index/MappingBuilder.php
index fc67420..e03bf54 100644
--- a/Index/MappingBuilder.php
+++ b/Index/MappingBuilder.php
@@ -18,7 +18,7 @@ class MappingBuilder
{
/**
* Skip adding default information to certain fields.
- *
+ *
* @var array
*/
private $skipTypes = array('completion');
@@ -27,6 +27,7 @@ class MappingBuilder
* Builds mappings for an entire index.
*
* @param IndexConfig $indexConfig
+ *
* @return array
*/
public function buildIndexMapping(IndexConfig $indexConfig)
@@ -36,13 +37,14 @@ class MappingBuilder
$typeMappings[$typeConfig->getName()] = $this->buildTypeMapping($typeConfig);
}
- $mapping = array(
- 'mappings' => $typeMappings,
- // 'warmers' => $indexConfig->getWarmers(),
- );
+ $mapping = array();
+ if (!empty($typeMappings)) {
+ $mapping['mappings'] = $typeMappings;
+ }
+ // 'warmers' => $indexConfig->getWarmers(),
$settings = $indexConfig->getSettings();
- if ($settings) {
+ if (!empty($settings)) {
$mapping['settings'] = $settings;
}
@@ -53,30 +55,51 @@ class MappingBuilder
* Builds mappings for a single type.
*
* @param TypeConfig $typeConfig
+ *
* @return array
*/
public function buildTypeMapping(TypeConfig $typeConfig)
{
- $mapping = array_merge($typeConfig->getMapping(), array(
- // 'date_detection' => true,
- // 'dynamic_date_formats' => array()
- // 'dynamic_templates' => $typeConfig->getDynamicTemplates(),
- // 'index_analyzer' => $typeConfig->getIndexAnalyzer(),
- // 'numeric_detection' => false,
- // 'properties' => array(),
- // 'search_analyzer' => $typeConfig->getSearchAnalyzer(),
- ));
+ $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();
+ }
+
+ if ($typeConfig->getSearchAnalyzer()) {
+ $mapping['search_analyzer'] = $typeConfig->getSearchAnalyzer();
+ }
if (isset($mapping['dynamic_templates']) and empty($mapping['dynamic_templates'])) {
unset($mapping['dynamic_templates']);
}
$this->fixProperties($mapping['properties']);
+ if (!$mapping['properties']) {
+ unset($mapping['properties']);
+ }
if ($typeConfig->getModel()) {
$mapping['_meta']['model'] = $typeConfig->getModel();
}
+ if (empty($mapping)) {
+ // Empty mapping, we want it encoded as a {} instead of a []
+ $mapping = new \stdClass();
+ }
+
return $mapping;
}
@@ -89,9 +112,14 @@ class MappingBuilder
private function fixProperties(&$properties)
{
foreach ($properties as $name => &$property) {
+ unset($property['property_path']);
+
if (!isset($property['type'])) {
$property['type'] = 'string';
}
+ if ($property['type'] == 'multi_field' && isset($property['fields'])) {
+ $this->fixProperties($property['fields']);
+ }
if (isset($property['properties'])) {
$this->fixProperties($property['properties']);
}
diff --git a/Index/Resetter.php b/Index/Resetter.php
index 3f07fa1..2b39157 100644
--- a/Index/Resetter.php
+++ b/Index/Resetter.php
@@ -5,12 +5,13 @@ namespace FOS\ElasticaBundle\Index;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
-use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\ConfigManager;
-use FOS\ElasticaBundle\Elastica\Client;
+use FOS\ElasticaBundle\Event\IndexResetEvent;
+use FOS\ElasticaBundle\Event\TypeResetEvent;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
- * Deletes and recreates indexes
+ * Deletes and recreates indexes.
*/
class Resetter
{
@@ -20,10 +21,15 @@ class Resetter
private $aliasProcessor;
/***
- * @var \FOS\ElasticaBundle\Configuration\Manager
+ * @var ConfigManager
*/
private $configManager;
+ /**
+ * @var EventDispatcherInterface
+ */
+ private $dispatcher;
+
/**
* @var IndexManager
*/
@@ -34,21 +40,37 @@ class Resetter
*/
private $mappingBuilder;
- public function __construct(ConfigManager $configManager, IndexManager $indexManager, AliasProcessor $aliasProcessor, MappingBuilder $mappingBuilder)
- {
+ /**
+ * @param ConfigManager $configManager
+ * @param IndexManager $indexManager
+ * @param AliasProcessor $aliasProcessor
+ * @param MappingBuilder $mappingBuilder
+ * @param EventDispatcherInterface $eventDispatcher
+ */
+ public function __construct(
+ ConfigManager $configManager,
+ IndexManager $indexManager,
+ AliasProcessor $aliasProcessor,
+ MappingBuilder $mappingBuilder,
+ EventDispatcherInterface $eventDispatcher
+ ) {
$this->aliasProcessor = $aliasProcessor;
$this->configManager = $configManager;
+ $this->dispatcher = $eventDispatcher;
$this->indexManager = $indexManager;
$this->mappingBuilder = $mappingBuilder;
}
/**
- * Deletes and recreates all indexes
+ * Deletes and recreates all indexes.
+ *
+ * @param bool $populating
+ * @param bool $force
*/
- public function resetAllIndexes($populating = false)
+ public function resetAllIndexes($populating = false, $force = false)
{
foreach ($this->configManager->getIndexNames() as $name) {
- $this->resetIndex($name, $populating);
+ $this->resetIndex($name, $populating, $force);
}
}
@@ -57,14 +79,19 @@ class Resetter
* with a randomised name for an alias to be set after population.
*
* @param string $indexName
- * @param bool $populating
+ * @param bool $populating
+ * @param bool $force If index exists with same name as alias, remove it
+ *
* @throws \InvalidArgumentException if no index exists for the given name
*/
- public function resetIndex($indexName, $populating = false)
+ public function resetIndex($indexName, $populating = false, $force = false)
{
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
$index = $this->indexManager->getIndex($indexName);
+ $event = new IndexResetEvent($indexName, $populating, $force);
+ $this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
+
if ($indexConfig->isUseAlias()) {
$this->aliasProcessor->setRootName($indexConfig, $index);
}
@@ -73,15 +100,18 @@ class Resetter
$index->create($mapping, true);
if (!$populating and $indexConfig->isUseAlias()) {
- $this->aliasProcessor->switchIndexAlias($indexConfig, $index);
+ $this->aliasProcessor->switchIndexAlias($indexConfig, $index, $force);
}
+
+ $this->dispatcher->dispatch(IndexResetEvent::POST_INDEX_RESET, $event);
}
/**
- * Deletes and recreates a mapping type for the named index
+ * Deletes and recreates a mapping type for the named index.
*
* @param string $indexName
* @param string $typeName
+ *
* @throws \InvalidArgumentException if no index or type mapping exists for the given names
* @throws ResponseException
*/
@@ -90,6 +120,9 @@ class Resetter
$typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName);
$type = $this->indexManager->getIndex($indexName)->getType($typeName);
+ $event = new TypeResetEvent($indexName, $typeName);
+ $this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
+
try {
$type->delete();
} catch (ResponseException $e) {
@@ -98,18 +131,20 @@ class Resetter
}
}
- $mapping = new Mapping;
+ $mapping = new Mapping();
foreach ($this->mappingBuilder->buildTypeMapping($typeConfig) as $name => $field) {
$mapping->setParam($name, $field);
}
$type->setMapping($mapping);
+
+ $this->dispatcher->dispatch(TypeResetEvent::POST_TYPE_RESET, $event);
}
/**
* A command run when a population has finished.
*
- * @param $indexName
+ * @param string $indexName
*/
public function postPopulate($indexName)
{
diff --git a/Manager/RepositoryManager.php b/Manager/RepositoryManager.php
index 3cf8e96..1a0601c 100644
--- a/Manager/RepositoryManager.php
+++ b/Manager/RepositoryManager.php
@@ -25,13 +25,13 @@ class RepositoryManager implements RepositoryManagerInterface
public function addEntity($entityName, FinderInterface $finder, $repositoryName = null)
{
- $this->entities[$entityName]= array();
+ $this->entities[$entityName] = array();
$this->entities[$entityName]['finder'] = $finder;
$this->entities[$entityName]['repositoryName'] = $repositoryName;
}
/**
- * Return repository for entity
+ * Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
@@ -59,16 +59,20 @@ class RepositoryManager implements RepositoryManagerInterface
}
$refClass = new \ReflectionClass($entityName);
- $annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Configuration\\Search');
+ $annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Annotation\\Search');
if ($annotation) {
$this->entities[$entityName]['repositoryName']
= $annotation->repositoryClass;
+
return $annotation->repositoryClass;
}
return 'FOS\ElasticaBundle\Repository';
}
+ /**
+ * @param string $entityName
+ */
private function createRepository($entityName)
{
if (!class_exists($repositoryName = $this->getRepositoryName($entityName))) {
diff --git a/Manager/RepositoryManagerInterface.php b/Manager/RepositoryManagerInterface.php
index 1008371..0a38e0e 100644
--- a/Manager/RepositoryManagerInterface.php
+++ b/Manager/RepositoryManagerInterface.php
@@ -12,7 +12,6 @@ use FOS\ElasticaBundle\Finder\FinderInterface;
*/
interface RepositoryManagerInterface
{
-
/**
* Adds entity name and its finder.
* Custom repository class name can also be added.
@@ -24,7 +23,7 @@ interface RepositoryManagerInterface
public function addEntity($entityName, FinderInterface $finder, $repositoryName = null);
/**
- * Return repository for entity
+ * Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
diff --git a/Paginator/FantaPaginatorAdapter.php b/Paginator/FantaPaginatorAdapter.php
index 2ad6983..8f9a60a 100644
--- a/Paginator/FantaPaginatorAdapter.php
+++ b/Paginator/FantaPaginatorAdapter.php
@@ -20,8 +20,6 @@ class FantaPaginatorAdapter implements AdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
- *
- * @api
*/
public function getNbResults()
{
@@ -29,15 +27,25 @@ class FantaPaginatorAdapter implements AdapterInterface
}
/**
- * Returns Facets
+ * Returns Facets.
+ *
+ * @return mixed
+ */
+ public function getFacets()
+ {
+ return $this->adapter->getFacets();
+ }
+
+ /**
+ * Returns Aggregations.
*
* @return mixed
*
* @api
*/
- public function getFacets()
+ public function getAggregations()
{
- return $this->adapter->getFacets();
+ return $this->adapter->getAggregations();
}
/**
@@ -47,8 +55,6 @@ class FantaPaginatorAdapter implements AdapterInterface
* @param integer $length The length.
*
* @return array|\Traversable The slice.
- *
- * @api
*/
public function getSlice($offset, $length)
{
diff --git a/Paginator/PaginatorAdapterInterface.php b/Paginator/PaginatorAdapterInterface.php
index 25786a0..adf7df2 100644
--- a/Paginator/PaginatorAdapterInterface.php
+++ b/Paginator/PaginatorAdapterInterface.php
@@ -8,10 +8,8 @@ interface PaginatorAdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
- *
- * @api
*/
- function getTotalHits();
+ public function getTotalHits();
/**
* Returns an slice of the results.
@@ -20,15 +18,20 @@ interface PaginatorAdapterInterface
* @param integer $length The length.
*
* @return PartialResultsInterface
- *
- * @api
*/
- function getResults($offset, $length);
+ public function getResults($offset, $length);
/**
- * Returns Facets
+ * Returns Facets.
*
* @return mixed
*/
- function getFacets();
+ public function getFacets();
+
+ /**
+ * Returns Aggregations.
+ *
+ * @return mixed
+ */
+ public function getAggregations();
}
diff --git a/Paginator/PartialResultsInterface.php b/Paginator/PartialResultsInterface.php
index 9efe7f3..156d27f 100644
--- a/Paginator/PartialResultsInterface.php
+++ b/Paginator/PartialResultsInterface.php
@@ -8,24 +8,27 @@ interface PartialResultsInterface
* Returns the paginated results.
*
* @return array
- *
- * @api
*/
- function toArray();
+ public function toArray();
/**
* Returns the number of results.
*
* @return integer The number of results.
- *
- * @api
*/
- function getTotalHits();
+ public function getTotalHits();
/**
- * Returns the facets
+ * Returns the facets.
*
* @return array
*/
- function getFacets();
-}
\ No newline at end of file
+ public function getFacets();
+
+ /**
+ * Returns the aggregations.
+ *
+ * @return array
+ */
+ public function getAggregations();
+}
diff --git a/Paginator/RawPaginatorAdapter.php b/Paginator/RawPaginatorAdapter.php
index 9136bc0..2eebde0 100644
--- a/Paginator/RawPaginatorAdapter.php
+++ b/Paginator/RawPaginatorAdapter.php
@@ -8,7 +8,7 @@ use Elastica\ResultSet;
use InvalidArgumentException;
/**
- * Allows pagination of Elastica\Query. Does not map results
+ * Allows pagination of Elastica\Query. Does not map results.
*/
class RawPaginatorAdapter implements PaginatorAdapterInterface
{
@@ -37,11 +37,16 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*/
private $facets;
+ /**
+ * @var array for the aggregations
+ */
+ private $aggregations;
+
/**
* @see PaginatorAdapterInterface::__construct
*
* @param SearchableInterface $searchable the object to search in
- * @param Query $query the query to search
+ * @param Query $query the query to search
* @param array $options
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options = array())
@@ -54,9 +59,11 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the paginated results.
*
- * @param $offset
- * @param $itemCountPerPage
+ * @param integer $offset
+ * @param integer $itemCountPerPage
+ *
* @throws \InvalidArgumentException
+ *
* @return ResultSet
*/
protected function getElasticaResults($offset, $itemCountPerPage)
@@ -67,7 +74,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
? (integer) $this->query->getParam('size')
: null;
- if ($size && $size < $offset + $itemCountPerPage) {
+ if (null !== $size && $size < $offset + $itemCountPerPage) {
$itemCountPerPage = $size - $offset;
}
@@ -82,6 +89,8 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
+ $this->aggregations = $resultSet->getAggregations();
+
return $resultSet;
}
@@ -90,6 +99,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*
* @param int $offset
* @param int $itemCountPerPage
+ *
* @return PartialResultsInterface
*/
public function getResults($offset, $itemCountPerPage)
@@ -100,27 +110,33 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the number of results.
*
+ * If genuineTotal is provided as true, total hits is returned from the
+ * hits.total value from the search results instead of just returning
+ * the requested size.
+ *
+ * @param boolean $genuineTotal
+ *
* @return integer The number of results.
*/
- public function getTotalHits()
+ public function getTotalHits($genuineTotal = false)
{
- if ( ! isset($this->totalHits)) {
- $this->totalHits = $this->searchable->search($this->query)->getTotalHits();
+ if (! isset($this->totalHits)) {
+ $this->totalHits = $this->searchable->count($this->query);
}
- return $this->query->hasParam('size')
+ return $this->query->hasParam('size') && !$genuineTotal
? min($this->totalHits, (integer) $this->query->getParam('size'))
: $this->totalHits;
}
/**
- * Returns Facets
+ * Returns Facets.
*
* @return mixed
*/
public function getFacets()
{
- if ( ! isset($this->facets)) {
+ if (! isset($this->facets)) {
$this->facets = $this->searchable->search($this->query)->getFacets();
}
@@ -128,7 +144,21 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
}
/**
- * Returns the Query
+ * Returns Aggregations.
+ *
+ * @return mixed
+ */
+ public function getAggregations()
+ {
+ if (!isset($this->aggregations)) {
+ $this->aggregations = $this->searchable->search($this->query)->getAggregations();
+ }
+
+ return $this->aggregations;
+ }
+
+ /**
+ * Returns the Query.
*
* @return Query the search query
*/
diff --git a/Paginator/RawPartialResults.php b/Paginator/RawPartialResults.php
index a4afb00..e45c6dd 100644
--- a/Paginator/RawPartialResults.php
+++ b/Paginator/RawPartialResults.php
@@ -6,7 +6,7 @@ use Elastica\ResultSet;
use Elastica\Result;
/**
- * Raw partial results transforms to a simple array
+ * Raw partial results transforms to a simple array.
*/
class RawPartialResults implements PartialResultsInterface
{
@@ -25,7 +25,7 @@ class RawPartialResults implements PartialResultsInterface
*/
public function toArray()
{
- return array_map(function(Result $result) {
+ return array_map(function (Result $result) {
return $result->getSource();
}, $this->resultSet->getResults());
}
@@ -47,6 +47,18 @@ class RawPartialResults implements PartialResultsInterface
return $this->resultSet->getFacets();
}
- return null;
+ return;
}
-}
\ No newline at end of file
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getAggregations()
+ {
+ if ($this->resultSet->hasAggregations()) {
+ return $this->resultSet->getAggregations();
+ }
+
+ return;
+ }
+}
diff --git a/Paginator/TransformedPaginatorAdapter.php b/Paginator/TransformedPaginatorAdapter.php
index 3b4716f..bf152fb 100644
--- a/Paginator/TransformedPaginatorAdapter.php
+++ b/Paginator/TransformedPaginatorAdapter.php
@@ -7,15 +7,15 @@ use Elastica\SearchableInterface;
use Elastica\Query;
/**
- * Allows pagination of \Elastica\Query
+ * Allows pagination of \Elastica\Query.
*/
class TransformedPaginatorAdapter extends RawPaginatorAdapter
{
private $transformer;
/**
- * @param SearchableInterface $searchable the object to search in
- * @param Query $query the query to search
+ * @param SearchableInterface $searchable the object to search in
+ * @param Query $query the query to search
* @param array $options
* @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
*/
diff --git a/Paginator/TransformedPartialResults.php b/Paginator/TransformedPartialResults.php
index 13d716c..c9470c3 100644
--- a/Paginator/TransformedPartialResults.php
+++ b/Paginator/TransformedPartialResults.php
@@ -6,14 +6,14 @@ use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Elastica\ResultSet;
/**
- * Partial transformed result set
+ * Partial transformed result set.
*/
class TransformedPartialResults extends RawPartialResults
{
protected $transformer;
/**
- * @param ResultSet $resultSet
+ * @param ResultSet $resultSet
* @param \FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface $transformer
*/
public function __construct(ResultSet $resultSet, ElasticaToModelTransformerInterface $transformer)
@@ -30,4 +30,4 @@ class TransformedPartialResults extends RawPartialResults
{
return $this->transformer->transform($this->resultSet->getResults());
}
-}
\ No newline at end of file
+}
diff --git a/Persister/ObjectPersister.php b/Persister/ObjectPersister.php
index 9604f7e..92dc005 100644
--- a/Persister/ObjectPersister.php
+++ b/Persister/ObjectPersister.php
@@ -4,14 +4,13 @@ namespace FOS\ElasticaBundle\Persister;
use Psr\Log\LoggerInterface;
use Elastica\Exception\BulkException;
-use Elastica\Exception\NotFoundException;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
use Elastica\Type;
use Elastica\Document;
/**
* Inserts, replaces and deletes single documents in an elastica type
- * Accepts domain model objects and converts them to elastica documents
+ * Accepts domain model objects and converts them to elastica documents.
*
* @author Thibault Duplessis
*/
@@ -35,6 +34,7 @@ class ObjectPersister implements ObjectPersisterInterface
* If the ObjectPersister handles a given object.
*
* @param object $object
+ *
* @return bool
*/
public function handlesObject($object)
@@ -42,17 +42,20 @@ class ObjectPersister implements ObjectPersisterInterface
return $object instanceof $this->objectClass;
}
+ /**
+ * @param LoggerInterface $logger
+ */
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
- * Log exception if logger defined for persister belonging to the current listener, otherwise re-throw
+ * Log exception if logger defined for persister belonging to the current listener, otherwise re-throw.
*
* @param BulkException $e
+ *
* @throws BulkException
- * @return null
*/
private function log(BulkException $e)
{
@@ -65,7 +68,7 @@ class ObjectPersister implements ObjectPersisterInterface
/**
* Insert one object into the type
- * The object will be transformed to an elastica document
+ * The object will be transformed to an elastica document.
*
* @param object $object
*/
@@ -75,10 +78,9 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Replaces one object in the type
+ * Replaces one object in the type.
*
* @param object $object
- * @return null
**/
public function replaceOne($object)
{
@@ -86,10 +88,9 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Deletes one object in the type
+ * Deletes one object in the type.
*
* @param object $object
- * @return null
**/
public function deleteOne($object)
{
@@ -97,11 +98,9 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Deletes one object in the type by id
+ * Deletes one object in the type by id.
*
* @param mixed $id
- *
- * @return null
**/
public function deleteById($id)
{
@@ -109,7 +108,7 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Bulk insert an array of objects in the type for the given method
+ * Bulk insert an array of objects in the type for the given method.
*
* @param array $objects array of domain model objects
* @param string Method to call
@@ -149,7 +148,7 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Bulk deletes an array of objects in the type
+ * Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
@@ -167,7 +166,7 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Bulk deletes records from an array of identifiers
+ * Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/
@@ -181,9 +180,10 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
- * Transforms an object to an elastica document
+ * Transforms an object to an elastica document.
*
* @param object $object
+ *
* @return Document the elastica document
*/
public function transformToElasticaDocument($object)
diff --git a/Persister/ObjectPersisterInterface.php b/Persister/ObjectPersisterInterface.php
index 2b4c8ee..f624971 100644
--- a/Persister/ObjectPersisterInterface.php
+++ b/Persister/ObjectPersisterInterface.php
@@ -4,66 +4,73 @@ namespace FOS\ElasticaBundle\Persister;
/**
* Inserts, replaces and deletes single documents in an elastica type
- * Accepts domain model objects and converts them to elastica documents
+ * Accepts domain model objects and converts them to elastica documents.
*
* @author Thibault Duplessis
*/
interface ObjectPersisterInterface
{
+ /**
+ * Checks if this persister can handle the given object or not.
+ *
+ * @param mixed $object
+ *
+ * @return boolean
+ */
+ public function handlesObject($object);
+
/**
* Insert one object into the type
- * The object will be transformed to an elastica document
+ * The object will be transformed to an elastica document.
*
* @param object $object
*/
- function insertOne($object);
+ public function insertOne($object);
/**
- * Replaces one object in the type
+ * Replaces one object in the type.
*
* @param object $object
**/
- function replaceOne($object);
+ public function replaceOne($object);
/**
- * Deletes one object in the type
+ * Deletes one object in the type.
*
* @param object $object
**/
- function deleteOne($object);
+ public function deleteOne($object);
/**
- * Deletes one object in the type by id
+ * Deletes one object in the type by id.
*
* @param mixed $id
- *
- * @return null
*/
- function deleteById($id);
+ public function deleteById($id);
/**
- * Bulk inserts an array of objects in the type
+ * Bulk inserts an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
- function insertMany(array $objects);
+ public function insertMany(array $objects);
/**
- * Bulk updates an array of objects in the type
+ * Bulk updates an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
- function replaceMany(array $objects);
+ public function replaceMany(array $objects);
/**
- * Bulk deletes an array of objects in the type
+ * Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
- function deleteMany(array $objects);
+ public function deleteMany(array $objects);
/**
- * Bulk deletes records from an array of identifiers
+ * Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/
diff --git a/Persister/ObjectSerializerPersister.php b/Persister/ObjectSerializerPersister.php
index 1a15656..792aa9a 100644
--- a/Persister/ObjectSerializerPersister.php
+++ b/Persister/ObjectSerializerPersister.php
@@ -9,7 +9,7 @@ use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
/**
* Inserts, replaces and deletes single objects in an elastica type, making use
* of elastica's serializer support to convert objects in to elastica documents.
- * Accepts domain model objects and passes them directly to elastica
+ * Accepts domain model objects and passes them directly to elastica.
*
* @author Lea Haensenberber
*/
@@ -17,17 +17,25 @@ class ObjectSerializerPersister extends ObjectPersister
{
protected $serializer;
+ /**
+ * @param Type $type
+ * @param ModelToElasticaTransformerInterface $transformer
+ * @param string $objectClass
+ * @param callable $serializer
+ */
public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer)
{
parent::__construct($type, $transformer, $objectClass, array());
- $this->serializer = $serializer;
+
+ $this->serializer = $serializer;
}
/**
* Transforms an object to an elastica document
- * with just the identifier set
+ * with just the identifier set.
*
* @param object $object
+ *
* @return Document the elastica document
*/
public function transformToElasticaDocument($object)
diff --git a/Propel/ElasticaToModelTransformer.php b/Propel/ElasticaToModelTransformer.php
index af5f8ab..d143478 100644
--- a/Propel/ElasticaToModelTransformer.php
+++ b/Propel/ElasticaToModelTransformer.php
@@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\HybridResult;
+use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -14,7 +15,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
*
* @author William Durand
*/
-class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
+class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
/**
* Propel model class to map to Elastica documents.
@@ -33,18 +34,11 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
'identifier' => 'id',
);
- /**
- * PropertyAccessor instance.
- *
- * @var PropertyAccessorInterface
- */
- protected $propertyAccessor;
-
/**
* Constructor.
*
* @param string $objectClass
- * @param array $options
+ * @param array $options
*/
public function __construct($objectClass, array $options = array())
{
@@ -52,21 +46,12 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$this->options = array_merge($this->options, $options);
}
- /**
- * Set the PropertyAccessor instance.
- *
- * @param PropertyAccessorInterface $propertyAccessor
- */
- public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
- {
- $this->propertyAccessor = $propertyAccessor;
- }
-
/**
* Transforms an array of Elastica document into an array of Propel entities
* fetched from the database.
*
* @param array $elasticaObjects
+ *
* @return array|\ArrayObject
*/
public function transform(array $elasticaObjects)
@@ -81,11 +66,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
// Sort objects in the order of their IDs
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
- $propertyAccessor = $this->propertyAccessor;
-
- $sortCallback = function($a, $b) use ($idPos, $identifier, $propertyAccessor) {
- return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
- };
+ $sortCallback = $this->getSortingClosure($idPos, $identifier);
if (is_object($objects)) {
$objects->uasort($sortCallback);
@@ -104,7 +85,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$objects = $this->transform($elasticaObjects);
$result = array();
- for ($i = 0; $i < count($elasticaObjects); $i++) {
+ for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
}
@@ -135,6 +116,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
*
* @param array $identifierValues Identifier values
* @param boolean $hydrate Whether or not to hydrate the results
+ *
* @return array
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
@@ -145,7 +127,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$query = $this->createQuery($this->objectClass, $this->options['identifier'], $identifierValues);
- if ( ! $hydrate) {
+ if (! $hydrate) {
return $query->toArray();
}
@@ -158,6 +140,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
* @param string $class Propel model class
* @param string $identifierField Identifier field name (e.g. "id")
* @param array $identifierValues Identifier values
+ *
* @return \ModelCriteria
*/
protected function createQuery($class, $identifierField, array $identifierValues)
@@ -170,6 +153,8 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
/**
* @see https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Util/Inflector.php
+ *
+ * @param string $str
*/
private function camelize($str)
{
diff --git a/Propel/Provider.php b/Propel/Provider.php
index 38f7a61..9864d53 100644
--- a/Propel/Provider.php
+++ b/Propel/Provider.php
@@ -5,53 +5,69 @@ namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\Provider\AbstractProvider;
/**
- * Propel provider
+ * Propel provider.
*
* @author William Durand
*/
class Provider extends AbstractProvider
{
/**
- * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
+ * {@inheritDoc}
*/
- public function populate(\Closure $loggerClosure = null, array $options = array())
+ public function doPopulate($options, \Closure $loggerClosure = null)
{
- $queryClass = $this->objectClass . 'Query';
+ $queryClass = $this->objectClass.'Query';
$nbObjects = $queryClass::create()->count();
- $offset = isset($options['offset']) ? intval($options['offset']) : 0;
- $sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
- $batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
- for (; $offset < $nbObjects; $offset += $batchSize) {
- if ($loggerClosure) {
- $stepStartTime = microtime(true);
- }
+ $offset = $options['offset'];
+ for (; $offset < $nbObjects; $offset += $options['batch_size']) {
$objects = $queryClass::create()
- ->limit($batchSize)
+ ->limit($options['batch_size'])
->offset($offset)
->find()
->getArrayCopy();
- if ($loggerClosure) {
- $stepNbObjects = count($objects);
- }
- $objects = array_filter($objects, array($this, 'isObjectIndexable'));
- if (!$objects) {
- $loggerClosure('Entire batch was filtered away, skipping...');
-
- continue;
+ $objects = $this->filterObjects($options, $objects);
+ if (!empty($objects)) {
+ $this->objectPersister->insertMany($objects);
}
- $this->objectPersister->insertMany($objects);
-
- usleep($sleep);
+ usleep($options['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($options['batch_size'], $nbObjects);
}
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function configureOptions()
+ {
+ parent::configureOptions();
+
+ $this->resolver->setDefaults(array(
+ 'clear_object_manager' => true,
+ 'debug_logging' => false,
+ 'ignore_errors' => false,
+ 'offset' => 0,
+ 'query_builder_method' => null,
+ 'sleep' => 0
+ ));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function disableLogging()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function enableLogging($logger)
+ {
+ }
}
diff --git a/Provider/AbstractProvider.php b/Provider/AbstractProvider.php
index 82ea914..f05ab98 100644
--- a/Provider/AbstractProvider.php
+++ b/Provider/AbstractProvider.php
@@ -3,16 +3,17 @@
namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
/**
- * AbstractProvider
+ * AbstractProvider.
*/
abstract class AbstractProvider implements ProviderInterface
{
/**
- * @var ObjectPersisterInterface
+ * @var array
*/
- protected $objectPersister;
+ protected $baseOptions;
/**
* @var string
@@ -20,12 +21,17 @@ abstract class AbstractProvider implements ProviderInterface
protected $objectClass;
/**
- * @var array
+ * @var ObjectPersisterInterface
*/
- protected $options;
+ protected $objectPersister;
/**
- * @var Indexable
+ * @var OptionsResolver
+ */
+ protected $resolver;
+
+ /**
+ * @var IndexableInterface
*/
private $indexable;
@@ -33,42 +39,136 @@ abstract class AbstractProvider implements ProviderInterface
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param IndexableInterface $indexable
- * @param string $objectClass
- * @param array $options
+ * @param IndexableInterface $indexable
+ * @param string $objectClass
+ * @param array $baseOptions
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
- array $options = array()
+ array $baseOptions = array()
) {
+ $this->baseOptions = $baseOptions;
$this->indexable = $indexable;
$this->objectClass = $objectClass;
$this->objectPersister = $objectPersister;
+ $this->resolver = new OptionsResolver();
+ $this->configureOptions();
+ }
- $this->options = array_merge(array(
+ /**
+ * {@inheritDoc}
+ */
+ public function populate(\Closure $loggerClosure = null, array $options = array())
+ {
+ $options = $this->resolveOptions($options);
+
+ $logger = !$options['debug_logging'] ?
+ $this->disableLogging() :
+ null;
+
+ $this->doPopulate($options, $loggerClosure);
+
+ if (null !== $logger) {
+ $this->enableLogging($logger);
+ }
+ }
+
+ /**
+ * Disables logging and returns the logger that was previously set.
+ *
+ * @return mixed
+ */
+ abstract protected function disableLogging();
+
+ /**
+ * Perform actual population.
+ *
+ * @param array $options
+ * @param \Closure $loggerClosure
+ */
+ abstract protected function doPopulate($options, \Closure $loggerClosure = null);
+
+ /**
+ * Reenables the logger with the previously returned logger from disableLogging();.
+ *
+ * @param mixed $logger
+ *
+ * @return mixed
+ */
+ abstract protected function enableLogging($logger);
+
+ /**
+ * Configures the option resolver.
+ */
+ protected function configureOptions()
+ {
+ $this->resolver->setDefaults(array(
'batch_size' => 100,
- ), $options);
+ 'skip_indexable_check' => false,
+ ));
+ $this->resolver->setAllowedTypes(array(
+ 'batch_size' => 'int'
+ ));
+
+ $this->resolver->setRequired(array(
+ 'indexName',
+ 'typeName',
+ ));
+ }
+
+
+ /**
+ * Filters objects away if they are not indexable.
+ *
+ * @param array $options
+ * @param array $objects
+ * @return array
+ */
+ protected function filterObjects(array $options, array $objects)
+ {
+ if ($options['skip_indexable_check']) {
+ return $objects;
+ }
+
+ $index = $options['indexName'];
+ $type = $options['typeName'];
+
+ $return = array();
+ foreach ($objects as $object) {
+ if (!$this->indexable->isObjectIndexable($index, $type, $object)) {
+ continue;
+ }
+
+ $return[] = $object;
+ }
+
+ return $return;
}
/**
* Checks if a given object should be indexed or not.
*
+ * @deprecated To be removed in 4.0
+ *
* @param object $object
+ *
* @return bool
*/
protected function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
- $this->options['indexName'],
- $this->options['typeName'],
+ $this->baseOptions['indexName'],
+ $this->baseOptions['typeName'],
$object
);
}
/**
- * 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
*/
@@ -79,4 +179,17 @@ abstract class AbstractProvider implements ProviderInterface
return sprintf('(RAM : current=%uMo peak=%uMo)', $memory, $memoryMax);
}
+
+ /**
+ * Merges the base options provided by the class with options passed to the populate
+ * method and runs them through the resolver.
+ *
+ * @param array $options
+ *
+ * @return array
+ */
+ protected function resolveOptions(array $options)
+ {
+ return $this->resolver->resolve(array_merge($this->baseOptions, $options));
+ }
}
diff --git a/Provider/Indexable.php b/Provider/Indexable.php
index 197aeb8..c26da5a 100644
--- a/Provider/Indexable.php
+++ b/Provider/Indexable.php
@@ -33,7 +33,7 @@ class Indexable implements IndexableInterface
private $container;
/**
- * An instance of ExpressionLanguage
+ * An instance of ExpressionLanguage.
*
* @var ExpressionLanguage
*/
@@ -47,7 +47,7 @@ class Indexable implements IndexableInterface
private $initialisedCallbacks = array();
/**
- * PropertyAccessor instance
+ * PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
@@ -55,6 +55,7 @@ class Indexable implements IndexableInterface
/**
* @param array $callbacks
+ * @param ContainerInterface $container
*/
public function __construct(array $callbacks, ContainerInterface $container)
{
@@ -68,7 +69,8 @@ class Indexable implements IndexableInterface
*
* @param string $indexName
* @param string $typeName
- * @param mixed $object
+ * @param mixed $object
+ *
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object)
@@ -80,9 +82,9 @@ class Indexable implements IndexableInterface
}
if ($callback instanceof Expression) {
- return $this->getExpressionLanguage()->evaluate($callback, array(
+ return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
- $this->getExpressionVar($object) => $object
+ $this->getExpressionVar($object) => $object,
));
}
@@ -96,12 +98,13 @@ class Indexable implements IndexableInterface
*
* @param string $type
* @param object $object
+ *
* @return mixed
*/
private function buildCallback($type, $object)
{
if (!array_key_exists($type, $this->callbacks)) {
- return null;
+ return;
}
$callback = $this->callbacks[$type];
@@ -110,44 +113,54 @@ class Indexable implements IndexableInterface
return $callback;
}
- if (is_array($callback)) {
- list($class, $method) = $callback + array(null, null);
-
- if (is_object($class)) {
- $class = get_class($class);
- }
-
- if (strpos($class, '@') === 0) {
- $service = $this->container->get(substr($class, 1));
-
- return array($service, $method);
- }
-
- if ($class && $method) {
- throw new \InvalidArgumentException(sprintf('Callback for type "%s", "%s::%s()", is not callable.', $type, $class, $method));
- }
+ if (is_array($callback) && !is_object($callback[0])) {
+ return $this->processArrayToCallback($type, $callback);
}
- if (is_string($callback) && $expression = $this->getExpressionLanguage()) {
- $callback = new Expression($callback);
-
- try {
- $expression->compile($callback, array('object', $this->getExpressionVar($object)));
-
- return $callback;
- } catch (SyntaxError $e) {
- throw new \InvalidArgumentException(sprintf('Callback for type "%s" is an invalid expression', $type), $e->getCode(), $e);
- }
+ if (is_string($callback)) {
+ return $this->buildExpressionCallback($type, $object, $callback);
}
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
}
+ /**
+ * Processes a string expression into an Expression.
+ *
+ * @param string $type
+ * @param mixed $object
+ * @param string $callback
+ *
+ * @return Expression
+ */
+ private function buildExpressionCallback($type, $object, $callback)
+ {
+ $expression = $this->getExpressionLanguage();
+ if (!$expression) {
+ throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
+ }
+
+ try {
+ $callback = new Expression($callback);
+ $expression->compile($callback, array(
+ 'object', $this->getExpressionVar($object)
+ ));
+
+ return $callback;
+ } catch (SyntaxError $e) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Callback for type "%s" is an invalid expression',
+ $type
+ ), $e->getCode(), $e);
+ }
+ }
+
/**
* Retreives a cached callback, or creates a new callback if one is not found.
*
* @param string $type
* @param object $object
+ *
* @return mixed
*/
private function getCallback($type, $object)
@@ -160,15 +173,13 @@ class Indexable implements IndexableInterface
}
/**
- * @return bool|ExpressionLanguage
+ * Returns the ExpressionLanguage class if it is available.
+ *
+ * @return ExpressionLanguage|null
*/
private function getExpressionLanguage()
{
- if (null === $this->expressionLanguage) {
- if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
- return false;
- }
-
+ if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$this->expressionLanguage = new ExpressionLanguage();
}
@@ -176,13 +187,54 @@ class Indexable implements IndexableInterface
}
/**
+ * Returns the variable name to be used to access the object when using the ExpressionLanguage
+ * component to parse and evaluate an expression.
+ *
* @param mixed $object
+ *
* @return string
*/
private function getExpressionVar($object = null)
{
+ if (!is_object($object)) {
+ return 'object';
+ }
+
$ref = new \ReflectionClass($object);
return strtolower($ref->getShortName());
}
+
+ /**
+ * Processes an array into a callback. Replaces the first element with a service if
+ * it begins with an @.
+ *
+ * @param string $type
+ * @param array $callback
+ * @return array
+ */
+ private function processArrayToCallback($type, array $callback)
+ {
+ list($class, $method) = $callback + array(null, '__invoke');
+
+ if (strpos($class, '@') === 0) {
+ $service = $this->container->get(substr($class, 1));
+ $callback = array($service, $method);
+
+ if (!is_callable($callback)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Method "%s" on service "%s" is not callable.',
+ $method,
+ substr($class, 1)
+ ));
+ }
+
+ return $callback;
+ }
+
+ throw new \InvalidArgumentException(sprintf(
+ 'Unable to parse callback array for type "%s"',
+ $type
+ ));
+ }
}
diff --git a/Provider/IndexableInterface.php b/Provider/IndexableInterface.php
index 4871b58..0d9f047 100644
--- a/Provider/IndexableInterface.php
+++ b/Provider/IndexableInterface.php
@@ -18,7 +18,8 @@ interface IndexableInterface
*
* @param string $indexName
* @param string $typeName
- * @param mixed $object
+ * @param mixed $object
+ *
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object);
diff --git a/Provider/ProviderInterface.php b/Provider/ProviderInterface.php
index e8d7ea4..1ba977b 100644
--- a/Provider/ProviderInterface.php
+++ b/Provider/ProviderInterface.php
@@ -3,7 +3,7 @@
namespace FOS\ElasticaBundle\Provider;
/**
- * Insert application domain objects into elastica types
+ * Insert application domain objects into elastica types.
*
* @author Thibault Duplessis
*/
@@ -12,9 +12,15 @@ interface ProviderInterface
/**
* Persists all domain objects to ElasticSearch for this provider.
*
+ * The closure can expect 2 or 3 arguments:
+ * * The step size
+ * * The total number of objects
+ * * A message to output in error conditions (not normally provided)
+ *
* @param \Closure $loggerClosure
* @param array $options
+ *
* @return
*/
- function populate(\Closure $loggerClosure = null, array $options = array());
+ public function populate(\Closure $loggerClosure = null, array $options = array());
}
diff --git a/Provider/ProviderRegistry.php b/Provider/ProviderRegistry.php
index 2142223..9fc9e3c 100644
--- a/Provider/ProviderRegistry.php
+++ b/Provider/ProviderRegistry.php
@@ -57,8 +57,10 @@ class ProviderRegistry implements ContainerAwareInterface
*
* Providers will be indexed by "type" strings in the returned array.
*
- * @param string $index
- * @return array of ProviderInterface instances
+ * @param string $index
+ *
+ * @return ProviderInterface[]
+ *
* @throws \InvalidArgumentException if no providers were registered for the index
*/
public function getIndexProviders($index)
@@ -81,7 +83,9 @@ class ProviderRegistry implements ContainerAwareInterface
*
* @param string $index
* @param string $type
+ *
* @return ProviderInterface
+ *
* @throws \InvalidArgumentException if no provider was registered for the index and type
*/
public function getProvider($index, $type)
diff --git a/README.md b/README.md
index 631951a..797d629 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,16 @@ Symfony2. Features include:
> **Note** Propel support is limited and contributions fixing issues are welcome!
[](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [](https://packagist.org/packages/friendsofsymfony/elastica-bundle)
+[](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSElasticaBundle/?branch=master)
Documentation
-------------
Documentation for FOSElasticaBundle is in `Resources/doc/index.md`
-[Read the documentation for 3.0.x (master)](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
+[Read the documentation for 3.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
-[Read the documentation for 2.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/2.1.x/README.md)
+[Read the documentation for 3.0.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/3.0.x/Resources/doc/index.md)
Installation
------------
diff --git a/Repository.php b/Repository.php
index 70b2a21..04a51c5 100644
--- a/Repository.php
+++ b/Repository.php
@@ -19,21 +19,47 @@ class Repository
$this->finder = $finder;
}
+ /**
+ * @param mixed $query
+ * @param integer $limit
+ * @param array $options
+ *
+ * @return array
+ */
public function find($query, $limit = null, $options = array())
{
return $this->finder->find($query, $limit, $options);
}
+ /**
+ * @param mixed $query
+ * @param integer $limit
+ * @param array $options
+ *
+ * @return mixed
+ */
public function findHybrid($query, $limit = null, $options = array())
{
return $this->finder->findHybrid($query, $limit, $options);
}
+ /**
+ * @param mixed $query
+ * @param array $options
+ *
+ * @return \Pagerfanta\Pagerfanta
+ */
public function findPaginated($query, $options = array())
{
return $this->finder->findPaginated($query, $options);
}
+ /**
+ * @param string $query
+ * @param array $options
+ *
+ * @return Paginator\PaginatorAdapterInterface
+ */
public function createPaginatorAdapter($query, $options = array())
{
return $this->finder->createPaginatorAdapter($query, $options);
diff --git a/Resources/config/index.xml b/Resources/config/index.xml
index 11586ff..3ae2e50 100644
--- a/Resources/config/index.xml
+++ b/Resources/config/index.xml
@@ -41,6 +41,7 @@
+
diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml
index 048d799..97ed16e 100644
--- a/Resources/config/mongodb.xml
+++ b/Resources/config/mongodb.xml
@@ -4,24 +4,35 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ FOS\ElasticaBundle\Doctrine\MongoDB\SliceFetcher
+ FOS\ElasticaBundle\Doctrine\MongoDB\Provider
+ FOS\ElasticaBundle\Doctrine\Listener
+ FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer
+ FOS\ElasticaBundle\Doctrine\RepositoryManager
+
+
-
+
+
+
+
-
+
+
-
+
-
-
+ null
-
+
@@ -30,7 +41,7 @@
-
+
diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml
index ddc0e50..8147d51 100644
--- a/Resources/config/orm.xml
+++ b/Resources/config/orm.xml
@@ -4,24 +4,35 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ FOS\ElasticaBundle\Doctrine\ORM\SliceFetcher
+ FOS\ElasticaBundle\Doctrine\ORM\Provider
+ FOS\ElasticaBundle\Doctrine\Listener
+ FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer
+ FOS\ElasticaBundle\Doctrine\RepositoryManager
+
+
-
+
+
+
+
-
+
+
-
+
-
-
+ null
-
+
@@ -30,7 +41,7 @@
-
+
diff --git a/Resources/config/transformer.xml b/Resources/config/transformer.xml
index 4ce5062..0957152 100644
--- a/Resources/config/transformer.xml
+++ b/Resources/config/transformer.xml
@@ -12,7 +12,8 @@
-
+
+
diff --git a/Resources/doc/cookbook/custom-properties.md b/Resources/doc/cookbook/custom-properties.md
new file mode 100644
index 0000000..1d7687e
--- /dev/null
+++ b/Resources/doc/cookbook/custom-properties.md
@@ -0,0 +1,33 @@
+##### Custom Properties
+
+Since FOSElasticaBundle 3.1.0, we now dispatch an event for each transformation of an
+object into an Elastica document which allows you to set custom properties on the Elastica
+document for indexing.
+
+Set up an event listener or subscriber for
+`FOS\ElasticaBundle\Event\TransformEvent::POST_TRANSFORM` to be able to inject your own
+parameters.
+
+```php
+class CustomPropertyListener implements EventSubscriberInterface
+{
+ private $anotherService;
+
+ // ...
+
+ public function addCustomProperty(TransformEvent $event)
+ {
+ $document = $event->getDocument();
+ $custom = $this->anotherService->calculateCustom($event->getObject());
+
+ $document->set('custom', $custom);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ TransformEvent::POST_TRANSFORM => 'addCustomProperty',
+ );
+ }
+}
+```
diff --git a/Resources/doc/cookbook/custom-repositories.md b/Resources/doc/cookbook/custom-repositories.md
index 47dc3fe..866f72d 100644
--- a/Resources/doc/cookbook/custom-repositories.md
+++ b/Resources/doc/cookbook/custom-repositories.md
@@ -4,7 +4,7 @@ As well as the default repository you can create a custom repository for an enti
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
-```
+```php
get('fos_elastica.manager');
+```php
+/** var FOS\ElasticaBundle\Manager\RepositoryManager */
+$repositoryManager = $container->get('fos_elastica.manager');
- /** var FOS\ElasticaBundle\Repository */
- $repository = $repositoryManager->getRepository('UserBundle:User');
+/** var FOS\ElasticaBundle\Repository */
+$repository = $repositoryManager->getRepository('UserBundle:User');
- /** var array of Acme\UserBundle\Entity\User */
- $users = $repository->findWithCustomQuery('bob');
+/** var array of Acme\UserBundle\Entity\User */
+$users = $repository->findWithCustomQuery('bob');
+```
Alternatively you can specify the custom repository using an annotation in the entity:
-```
+```php
indexableUsername`, and the indexed field `firstName` would be populated from a
+key `first` from an array on `User->names`.
+
+Setting the property path to `false` will disable transformation of that value. In this
+case the mapping will be created but no value will be populated while indexing. You can
+populate this value by listening to the `POST_TRANSFORM` event emitted by this bundle.
+See [cookbook/custom-properties.md](cookbook/custom-properties.md) for more information
+about this event.
+
Handling missing results with FOSElasticaBundle
-----------------------------------------------
@@ -173,13 +201,18 @@ index enabled users.
The callback option supports multiple approaches:
* A method on the object itself provided as a string. `enabled` will call
- `Object->enabled()`
+ `Object->enabled()`. Note that this does not support chaining methods with dot notation
+ like property paths. To achieve something similar use the ExpressionLanguage option
+ below.
* An array of a service id and a method which will be called with the object as the first
and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
method on a service defined as my_custom_service.
* An array of a class and a static method to call on that class which will be called with
the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
will call Acme\DemoBundle\IndexableChecker::isIndexable($object)
+* A single element array with a service id can be used if the service has an __invoke
+ method. Such an invoke method must accept a single parameter for the object to be indexed.
+ `[ @my_custom_invokable_service ]`
* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
expression provided as a string. The object being indexed will be supplied as `object`
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more
diff --git a/Resources/doc/usage.md b/Resources/doc/usage.md
index 55d90ab..be11dbf 100644
--- a/Resources/doc/usage.md
+++ b/Resources/doc/usage.md
@@ -26,7 +26,9 @@ $userPaginator = $finder->findPaginated('bob');
$countOfResults = $userPaginator->getNbResults();
// Option 3b. KnpPaginator resultset
-
+$paginator = $this->get('knp_paginator');
+$results = $finder->createPaginatorAdapter('bob');
+$pagination = $paginator->paginate($results, $page, 10);
```
Faceted Searching
@@ -45,7 +47,7 @@ $companies = $finder->findPaginated($query);
$companies->setMaxPerPage($params['limit']);
$companies->setCurrentPage($params['page']);
-$facets = $companies->getAdapter()->getFacets());
+$facets = $companies->getAdapter()->getFacets();
```
Searching the entire index
@@ -160,7 +162,7 @@ fos_elastica:
site:
settings:
index:
- analysis:
+ analysis:
analyzer:
my_analyzer:
type: snowball
diff --git a/Resources/public/images/elastica.png b/Resources/public/images/elastica.png
deleted file mode 100644
index dbde014..0000000
Binary files a/Resources/public/images/elastica.png and /dev/null differ
diff --git a/Resources/views/Collector/elastica.html.twig b/Resources/views/Collector/elastica.html.twig
index e6d7072..82a3dcf 100644
--- a/Resources/views/Collector/elastica.html.twig
+++ b/Resources/views/Collector/elastica.html.twig
@@ -23,7 +23,7 @@
{% block menu %}
-
+
Elastica
{{ collector.querycount }}
diff --git a/Serializer/Callback.php b/Serializer/Callback.php
index 38d93dc..61da997 100644
--- a/Serializer/Callback.php
+++ b/Serializer/Callback.php
@@ -8,7 +8,7 @@ use JMS\Serializer\SerializerInterface;
class Callback
{
protected $serializer;
- protected $groups;
+ protected $groups = array();
protected $version;
public function setSerializer($serializer)
@@ -23,10 +23,8 @@ class Callback
{
$this->groups = $groups;
- if ($this->groups) {
- if (!$this->serializer instanceof SerializerInterface) {
- throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
- }
+ if (!empty($this->groups) && !$this->serializer instanceof SerializerInterface) {
+ throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
}
}
@@ -34,10 +32,8 @@ class Callback
{
$this->version = $version;
- if ($this->version) {
- if (!$this->serializer instanceof SerializerInterface) {
- throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
- }
+ if ($this->version && !$this->serializer instanceof SerializerInterface) {
+ throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
}
}
@@ -45,7 +41,7 @@ class Callback
{
$context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array();
- if ($this->groups) {
+ if (!empty($this->groups)) {
$context->setGroups($this->groups);
}
diff --git a/Subscriber/PaginateElasticaQuerySubscriber.php b/Subscriber/PaginateElasticaQuerySubscriber.php
index 0b7cfd6..63f6cd0 100644
--- a/Subscriber/PaginateElasticaQuerySubscriber.php
+++ b/Subscriber/PaginateElasticaQuerySubscriber.php
@@ -32,13 +32,17 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
if (null != $facets) {
$event->setCustomPaginationParameter('facets', $facets);
}
+ $aggregations = $results->getAggregations();
+ if (null != $aggregations) {
+ $event->setCustomPaginationParameter('aggregations', $aggregations);
+ }
$event->stopPropagation();
}
}
/**
- * Adds knp paging sort to query
+ * Adds knp paging sort to query.
*
* @param ItemsEvent $event
*/
@@ -70,7 +74,7 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
- 'knp_pager.items' => array('items', 1)
+ 'knp_pager.items' => array('items', 1),
);
}
-}
\ No newline at end of file
+}
diff --git a/Tests/Command/ResetCommandTest.php b/Tests/Command/ResetCommandTest.php
index b6548aa..d63b380 100644
--- a/Tests/Command/ResetCommandTest.php
+++ b/Tests/Command/ResetCommandTest.php
@@ -2,7 +2,6 @@
namespace FOS\ElasticaBundle\Tests\Command;
-
use FOS\ElasticaBundle\Command\ResetCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
@@ -10,8 +9,8 @@ use Symfony\Component\DependencyInjection\Container;
class ResetCommandTest extends \PHPUnit_Framework_TestCase
{
+ private $command;
private $resetter;
-
private $indexManager;
public function setup()
@@ -88,4 +87,4 @@ class ResetCommandTest extends \PHPUnit_Framework_TestCase
new NullOutput()
);
}
-}
\ No newline at end of file
+}
diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php
index 7165052..062db5c 100644
--- a/Tests/DependencyInjection/ConfigurationTest.php
+++ b/Tests/DependencyInjection/ConfigurationTest.php
@@ -6,7 +6,7 @@ use FOS\ElasticaBundle\DependencyInjection\Configuration;
use Symfony\Component\Config\Definition\Processor;
/**
- * ConfigurationTest
+ * ConfigurationTest.
*/
class ConfigurationTest extends \PHPUnit_Framework_TestCase
{
@@ -34,7 +34,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array(
'clients' => array(),
'indexes' => array(),
- 'default_manager' => 'orm'
+ 'default_manager' => 'orm',
), $configuration);
}
@@ -50,18 +50,18 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
array(
'url' => 'http://es1:9200',
'headers' => array(
- 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
- )
+ 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
+ ),
),
array(
'url' => 'http://es2:9200',
'headers' => array(
- 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
- )
+ 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
+ ),
),
- )
- )
- )
+ ),
+ ),
+ ),
));
$this->assertCount(2, $configuration['clients']);
@@ -91,9 +91,9 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
),
'logging_custom' => array(
'url' => 'http://localhost:9200',
- 'logger' => 'custom.service'
+ 'logger' => 'custom.service',
),
- )
+ ),
));
$this->assertCount(4, $configuration['clients']);
@@ -131,8 +131,8 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
),
'serializer' => array(
'groups' => array('Search'),
- 'version' => 1
- )
+ 'version' => 1,
+ ),
),
'types' => array(
'test' => array(
@@ -144,20 +144,20 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'persistence' => array(
'listener' => array(
'logger' => true,
- )
- )
+ ),
+ ),
),
'test2' => array(
'mappings' => array(
'title' => null,
'children' => array(
'type' => 'nested',
- )
- )
- )
- )
- )
- )
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
));
}
@@ -169,7 +169,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'host' => 'localhost',
'port' => 9200,
),
- )
+ ),
));
$this->assertTrue(empty($configuration['clients']['default']['connections'][0]['url']));
@@ -189,16 +189,34 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'title' => array(),
'published' => array('type' => 'datetime'),
'body' => null,
- )
- )
- )
- )
- )
+ ),
+ ),
+ ),
+ ),
+ ),
));
$this->assertCount(3, $configuration['indexes']['test']['types']['test']['properties']);
}
+ public function testUnconfiguredType()
+ {
+ $configuration = $this->getConfigs(array(
+ 'clients' => array(
+ 'default' => array('url' => 'http://localhost:9200'),
+ ),
+ 'indexes' => array(
+ 'test' => array(
+ 'types' => array(
+ 'test' => null,
+ ),
+ ),
+ ),
+ ));
+
+ $this->assertArrayHasKey('properties', $configuration['indexes']['test']['types']['test']);
+ }
+
public function testNestedProperties()
{
$this->getConfigs(array(
@@ -225,23 +243,23 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'type' => 'nested',
'properties' => array(
'nested_field1' => array(
- 'type' => 'integer'
+ 'type' => 'integer',
),
'nested_field2' => array(
'type' => 'object',
'properties' => array(
'id' => array(
- 'type' => 'integer'
- )
- )
- )
- )
- )
- )
- )
- )
- )
- )
+ 'type' => 'integer',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
));
}
}
diff --git a/Tests/DependencyInjection/FOSElasticaExtensionTest.php b/Tests/DependencyInjection/FOSElasticaExtensionTest.php
new file mode 100644
index 0000000..1bef2b6
--- /dev/null
+++ b/Tests/DependencyInjection/FOSElasticaExtensionTest.php
@@ -0,0 +1,32 @@
+setParameter('kernel.debug', true);
+
+ $extension = new FOSElasticaExtension();
+
+ $extension->load($config, $containerBuilder);
+
+ $this->assertTrue($containerBuilder->hasDefinition('fos_elastica.object_persister.test_index.child_field'));
+
+ $persisterCallDefinition = $containerBuilder->getDefinition('fos_elastica.object_persister.test_index.child_field');
+
+ $arguments = $persisterCallDefinition->getArguments();
+ $arguments = $arguments['index_3'];
+
+ $this->assertArrayHasKey('_parent', $arguments);
+ $this->assertEquals('parent_field', $arguments['_parent']['type']);
+ }
+}
diff --git a/Tests/DependencyInjection/fixtures/config.yml b/Tests/DependencyInjection/fixtures/config.yml
new file mode 100644
index 0000000..5528d18
--- /dev/null
+++ b/Tests/DependencyInjection/fixtures/config.yml
@@ -0,0 +1,21 @@
+fos_elastica:
+ clients:
+ default:
+ url: http://localhost:9200
+ indexes:
+ test_index:
+ client: default
+ types:
+ parent_field:
+ mappings:
+ text: ~
+ persistence:
+ driver: orm
+ model: foo_model
+ child_field:
+ mappings:
+ text: ~
+ persistence:
+ driver: orm
+ model: foo_model
+ _parent: { type: "parent_field", property: "parent" }
diff --git a/Tests/Doctrine/AbstractElasticaToModelTransformerTest.php b/Tests/Doctrine/AbstractElasticaToModelTransformerTest.php
new file mode 100644
index 0000000..1185e74
--- /dev/null
+++ b/Tests/Doctrine/AbstractElasticaToModelTransformerTest.php
@@ -0,0 +1,62 @@
+getMock(
+ 'FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer',
+ array('findByIdentifiers'),
+ array($this->registry, $this->objectClass, array('ignore_missing' => true))
+ );
+
+ $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
+
+ $firstOrmResult = new \stdClass();
+ $firstOrmResult->id = 1;
+ $secondOrmResult = new \stdClass();
+ $secondOrmResult->id = 3;
+ $transformer->expects($this->once())
+ ->method('findByIdentifiers')
+ ->with(array(1, 2, 3))
+ ->willReturn(array($firstOrmResult, $secondOrmResult));
+
+ $firstElasticaResult = new Result(array('_id' => 1));
+ $secondElasticaResult = new Result(array('_id' => 2));
+ $thirdElasticaResult = new Result(array('_id' => 3));
+
+ $hybridResults = $transformer->hybridTransform(array($firstElasticaResult, $secondElasticaResult, $thirdElasticaResult));
+
+ $this->assertCount(2, $hybridResults);
+ $this->assertEquals($firstOrmResult, $hybridResults[0]->getTransformed());
+ $this->assertEquals($firstElasticaResult, $hybridResults[0]->getResult());
+ $this->assertEquals($secondOrmResult, $hybridResults[1]->getTransformed());
+ $this->assertEquals($thirdElasticaResult, $hybridResults[1]->getResult());
+ }
+
+ protected function setUp()
+ {
+ $this->registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
diff --git a/Tests/Doctrine/AbstractListenerTest.php b/Tests/Doctrine/AbstractListenerTest.php
index 1f238d6..dcaf7d6 100644
--- a/Tests/Doctrine/AbstractListenerTest.php
+++ b/Tests/Doctrine/AbstractListenerTest.php
@@ -3,7 +3,7 @@
namespace FOS\ElasticaBundle\Tests\Doctrine;
/**
- * See concrete MongoDB/ORM instances of this abstract test
+ * See concrete MongoDB/ORM instances of this abstract test.
*
* @author Richard Miller
*/
@@ -16,7 +16,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
@@ -35,7 +35,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, false);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
@@ -55,7 +55,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
@@ -89,7 +89,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
@@ -124,7 +124,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->getId(), current($listener->scheduledForDeletion));
@@ -157,7 +157,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
- $listener = $this->createListener($persister, array(), $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
+ $listener = $this->createListener($persister, $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->identifier, current($listener->scheduledForDeletion));
@@ -173,8 +173,14 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
abstract protected function getListenerClass();
+ /**
+ * @return string
+ */
abstract protected function getObjectManagerClass();
+ /**
+ * @return string
+ */
abstract protected function getClassMetadataClass();
private function createLifecycleEventArgs()
@@ -205,6 +211,11 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->getMock();
}
+ /**
+ * @param Listener\Entity $object
+ * @param string $indexName
+ * @param string $typeName
+ */
private function getMockPersister($object, $indexName, $typeName)
{
$mock = $this->getMockBuilder('FOS\ElasticaBundle\Persister\ObjectPersister')
@@ -235,6 +246,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
return $mock;
}
+ /**
+ * @param string $indexName
+ * @param string $typeName
+ * @param Listener\Entity $object
+ * @param boolean $return
+ */
private function getMockIndexable($indexName, $typeName, $object, $return = null)
{
$mock = $this->getMock('FOS\ElasticaBundle\Provider\IndexableInterface');
@@ -255,7 +272,11 @@ namespace FOS\ElasticaBundle\Tests\Doctrine\Listener;
class Entity
{
private $id;
+ public $identifier;
+ /**
+ * @param integer $id
+ */
public function __construct($id)
{
$this->id = $id;
@@ -266,4 +287,3 @@ class Entity
return $this->id;
}
}
-
diff --git a/Tests/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php
index 99ed2de..aa28a4c 100644
--- a/Tests/Doctrine/AbstractProviderTest.php
+++ b/Tests/Doctrine/AbstractProviderTest.php
@@ -13,13 +13,10 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
private $options;
private $managerRegistry;
private $indexable;
+ private $sliceFetcher;
public function setUp()
{
- if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
- $this->markTestSkipped('Doctrine Common is not available.');
- }
-
$this->objectClass = 'objectClass';
$this->options = array('debug_logging' => true, 'indexName' => 'index', 'typeName' => 'type');
@@ -32,6 +29,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('getManagerForClass')
->with($this->objectClass)
->will($this->returnValue($this->objectManager));
+
+ $this->sliceFetcher = $this->getMockSliceFetcher();
}
/**
@@ -45,6 +44,53 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$queryBuilder = new \stdClass();
+ $provider->expects($this->once())
+ ->method('createQueryBuilder')
+ ->will($this->returnValue($queryBuilder));
+
+ $provider->expects($this->once())
+ ->method('countObjects')
+ ->with($queryBuilder)
+ ->will($this->returnValue($nbObjects));
+
+ $this->indexable->expects($this->any())
+ ->method('isObjectIndexable')
+ ->with('index', 'type', $this->anything())
+ ->will($this->returnValue(true));
+
+ $previousSlice = array();
+
+ foreach ($objectsByIteration as $i => $objects) {
+ $offset = $objects[0] - 1;
+
+ $this->sliceFetcher->expects($this->at($i))
+ ->method('fetch')
+ ->with($queryBuilder, $batchSize, $offset, $previousSlice, array('id'))
+ ->will($this->returnValue($objects));
+
+ $this->objectManager->expects($this->at($i))
+ ->method('clear');
+
+ $previousSlice = $objects;
+ }
+
+ $this->objectPersister->expects($this->exactly(count($objectsByIteration)))
+ ->method('insertMany');
+
+ $provider->populate();
+ }
+
+ /**
+ * @dataProvider providePopulateIterations
+ */
+ public function testPopulateIterationsWithoutSliceFetcher($nbObjects, $objectsByIteration, $batchSize)
+ {
+ $this->options['batch_size'] = $batchSize;
+
+ $provider = $this->getMockAbstractProvider(false);
+
+ $queryBuilder = new \stdClass();
+
$provider->expects($this->once())
->method('createQueryBuilder')
->will($this->returnValue($queryBuilder));
@@ -84,7 +130,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
return array(
array(
100,
- array(range(1,100)),
+ array(range(1, 100)),
100,
),
array(
@@ -107,8 +153,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
- $provider->expects($this->any())
- ->method('fetchSlice')
+ $this->sliceFetcher->expects($this->any())
+ ->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@@ -122,6 +168,32 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$provider->populate();
}
+ public function testPopulateShouldClearObjectManagerForFilteredBatch()
+ {
+ $nbObjects = 1;
+ $objects = array(1);
+
+ $provider = $this->getMockAbstractProvider(true);
+
+ $provider->expects($this->any())
+ ->method('countObjects')
+ ->will($this->returnValue($nbObjects));
+
+ $this->sliceFetcher->expects($this->any())
+ ->method('fetch')
+ ->will($this->returnValue($objects));
+
+ $this->indexable->expects($this->any())
+ ->method('isObjectIndexable')
+ ->with('index', 'type', $this->anything())
+ ->will($this->returnValue(false));
+
+ $this->objectManager->expects($this->once())
+ ->method('clear');
+
+ $provider->populate();
+ }
+
public function testPopulateInvokesLoggerClosure()
{
$nbObjects = 1;
@@ -133,8 +205,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
- $provider->expects($this->any())
- ->method('fetchSlice')
+ $this->sliceFetcher->expects($this->any())
+ ->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@@ -165,8 +237,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
- $provider->expects($this->any())
- ->method('fetchSlice')
+ $this->sliceFetcher->expects($this->any())
+ ->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@@ -180,7 +252,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('Elastica\Exception\Bulk\ResponseException');
- $provider->populate(null, array('ignore-errors' => false));
+ $provider->populate(null, array('ignore_errors' => false));
}
public function testPopulateRunsIndexCallable()
@@ -192,8 +264,9 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$provider->expects($this->any())
->method('countObjects')
->will($this->returnValue($nbObjects));
- $provider->expects($this->any())
- ->method('fetchSlice')
+
+ $this->sliceFetcher->expects($this->any())
+ ->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->at(0))
@@ -205,18 +278,19 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->with('index', 'type', 2)
->will($this->returnValue(true));
-
$this->objectPersister->expects($this->once())
->method('insertMany')
- ->with(array(1 => 2));
+ ->with(array(2));
$provider->populate();
}
/**
+ * @param boolean $setSliceFetcher Whether or not to set the slice fetcher.
+ *
* @return \FOS\ElasticaBundle\Doctrine\AbstractProvider|\PHPUnit_Framework_MockObject_MockObject
*/
- private function getMockAbstractProvider()
+ private function getMockAbstractProvider($setSliceFetcher = true)
{
return $this->getMockForAbstractClass('FOS\ElasticaBundle\Doctrine\AbstractProvider', array(
$this->objectPersister,
@@ -224,6 +298,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->objectClass,
$this->options,
$this->managerRegistry,
+ $setSliceFetcher ? $this->sliceFetcher : null
));
}
@@ -233,7 +308,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
private function getMockBulkResponseException()
{
return $this->getMock('Elastica\Exception\Bulk\ResponseException', null, array(
- new ResponseSet(new Response(array()), array())
+ new ResponseSet(new Response(array()), array()),
));
}
@@ -250,7 +325,17 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
*/
private function getMockObjectManager()
{
- return $this->getMock(__NAMESPACE__ . '\ObjectManager');
+ $mock = $this->getMock(__NAMESPACE__.'\ObjectManager');
+
+ $mock->expects($this->any())
+ ->method('getClassMetadata')
+ ->will($this->returnSelf());
+
+ $mock->expects($this->any())
+ ->method('getIdentifierFieldNames')
+ ->will($this->returnValue(array('id')));
+
+ return $mock;
}
/**
@@ -268,6 +353,14 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
return $this->getMock('FOS\ElasticaBundle\Provider\IndexableInterface');
}
+
+ /**
+ * @return \FOS\ElasticaBundle\Doctrine\SliceFetcherInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockSliceFetcher()
+ {
+ return $this->getMock('FOS\ElasticaBundle\Doctrine\SliceFetcherInterface');
+ }
}
/**
@@ -276,5 +369,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
*/
interface ObjectManager
{
- function clear();
+ public function clear();
+ public function getClassMetadata();
+ public function getIdentifierFieldNames();
}
diff --git a/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php
index 14f3ffb..607aeef 100644
--- a/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php
+++ b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php
@@ -82,13 +82,6 @@ class ElasticaToModelTransformerTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
- $this->markTestSkipped('Doctrine Common is not present');
- }
- if (!class_exists('Doctrine\ORM\EntityManager')) {
- $this->markTestSkipped('Doctrine Common is not present');
- }
-
$this->registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')
->disableOriginalConstructor()
->getMock();
@@ -109,7 +102,7 @@ class ElasticaToModelTransformerTest extends \PHPUnit_Framework_TestCase
'findAll',
'findBy',
'findOneBy',
- 'getClassName'
+ 'getClassName',
));
$this->manager->expects($this->any())
diff --git a/Tests/Doctrine/ORM/ListenerTest.php b/Tests/Doctrine/ORM/ListenerTest.php
index 12a89b2..36cacc6 100644
--- a/Tests/Doctrine/ORM/ListenerTest.php
+++ b/Tests/Doctrine/ORM/ListenerTest.php
@@ -6,13 +6,6 @@ use FOS\ElasticaBundle\Tests\Doctrine\ListenerTest as BaseListenerTest;
class ListenerTest extends BaseListenerTest
{
- public function setUp()
- {
- if (!class_exists('Doctrine\ORM\EntityManager')) {
- $this->markTestSkipped('Doctrine ORM is not available.');
- }
- }
-
protected function getClassMetadataClass()
{
return 'Doctrine\ORM\Mapping\ClassMetadata';
diff --git a/Tests/Doctrine/RepositoryManagerTest.php b/Tests/Doctrine/RepositoryManagerTest.php
index ce7b14b..39f9c34 100644
--- a/Tests/Doctrine/RepositoryManagerTest.php
+++ b/Tests/Doctrine/RepositoryManagerTest.php
@@ -4,22 +4,19 @@ namespace FOS\ElasticaBundle\Tests\Doctrine;
use FOS\ElasticaBundle\Doctrine\RepositoryManager;
-class CustomRepository{}
+class CustomRepository
+{
+}
-class Entity{}
+class Entity
+{
+}
/**
* @author Richard Miller
*/
class RepositoryManagerTest extends \PHPUnit_Framework_TestCase
{
- public function setUp()
- {
- if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
- $this->markTestSkipped('Doctrine Common is not available.');
- }
- }
-
public function testThatGetRepositoryReturnsDefaultRepository()
{
/** @var $finderMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
diff --git a/Tests/Elastica/ClientTest.php b/Tests/Elastica/ClientTest.php
index 43ac7a2..158b553 100644
--- a/Tests/Elastica/ClientTest.php
+++ b/Tests/Elastica/ClientTest.php
@@ -5,11 +5,11 @@ namespace FOS\ElasticaBundle\Tests\Client;
use Elastica\Request;
use Elastica\Transport\Null as NullTransport;
-class LoggingClientTest extends \PHPUnit_Framework_TestCase
+class ClientTest extends \PHPUnit_Framework_TestCase
{
public function testRequestsAreLogged()
{
- $transport = new NullTransport;
+ $transport = new NullTransport();
$connection = $this->getMock('Elastica\Connection');
$connection->expects($this->any())->method('getTransportObject')->will($this->returnValue($transport));
diff --git a/Tests/FOSElasticaBundleTest.php b/Tests/FOSElasticaBundleTest.php
index 3828e8b..c9513db 100644
--- a/Tests/FOSElasticaBundleTest.php
+++ b/Tests/FOSElasticaBundleTest.php
@@ -3,7 +3,6 @@
namespace FOS\ElasticaBundle\Tests\Resetter;
use FOS\ElasticaBundle\FOSElasticaBundle;
-use Symfony\Component\DependencyInjection\Compiler\PassConfig;
class FOSElasticaBundleTest extends \PHPUnit_Framework_TestCase
{
@@ -17,7 +16,6 @@ class FOSElasticaBundleTest extends \PHPUnit_Framework_TestCase
->method('addCompilerPass')
->with($this->isInstanceOf('Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface'));
-
$bundle = new FOSElasticaBundle();
$bundle->build($container);
}
diff --git a/Tests/Functional/ClientTest.php b/Tests/Functional/ClientTest.php
new file mode 100644
index 0000000..8a6357a
--- /dev/null
+++ b/Tests/Functional/ClientTest.php
@@ -0,0 +1,48 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Functional;
+
+use Symfony\Bundle\FrameworkBundle\Client;
+
+/**
+ * @group functional
+ */
+class ClientTest extends WebTestCase
+{
+ public function testContainerSource()
+ {
+ $client = $this->createClient(array('test_case' => 'Basic'));
+
+ $es = $client->getContainer()->get('fos_elastica.client.default');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\RoundRobin', $es->getConnectionStrategy());
+
+ $es = $client->getContainer()->get('fos_elastica.client.second_server');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\RoundRobin', $es->getConnectionStrategy());
+
+ $es = $client->getContainer()->get('fos_elastica.client.third');
+ $this->assertInstanceOf('Elastica\\Connection\\Strategy\\Simple', $es->getConnectionStrategy());
+ }
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->deleteTmpDir('Basic');
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->deleteTmpDir('Basic');
+ }
+}
diff --git a/Tests/Functional/ConfigurationManagerTest.php b/Tests/Functional/ConfigurationManagerTest.php
index 6fdc1d7..a6028b7 100644
--- a/Tests/Functional/ConfigurationManagerTest.php
+++ b/Tests/Functional/ConfigurationManagerTest.php
@@ -26,7 +26,7 @@ class ConfigurationManagerTest extends WebTestCase
$index = $manager->getIndexConfiguration('index');
$this->assertEquals('index', $index->getName());
- $this->assertCount(2, $index->getTypes());
+ $this->assertGreaterThanOrEqual(2, count($index->getTypes()));
$this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('type'));
$this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('parent'));
}
@@ -47,6 +47,7 @@ class ConfigurationManagerTest extends WebTestCase
/**
* @param Client $client
+ *
* @return \FOS\ElasticaBundle\Configuration\ConfigManager
*/
private function getManager(Client $client)
diff --git a/Tests/Functional/IndexableCallbackTest.php b/Tests/Functional/IndexableCallbackTest.php
index 41ed402..3f84286 100644
--- a/Tests/Functional/IndexableCallbackTest.php
+++ b/Tests/Functional/IndexableCallbackTest.php
@@ -17,7 +17,7 @@ namespace FOS\ElasticaBundle\Tests\Functional;
class IndexableCallbackTest extends WebTestCase
{
/**
- * 2 reasons for this test:
+ * 2 reasons for this test:.
*
* 1) To test that the configuration rename from is_indexable_callback under the listener
* key is respected, and
diff --git a/Tests/Functional/MappingToElasticaTest.php b/Tests/Functional/MappingToElasticaTest.php
index 2474a1c..6f93b7e 100644
--- a/Tests/Functional/MappingToElasticaTest.php
+++ b/Tests/Functional/MappingToElasticaTest.php
@@ -31,6 +31,16 @@ class MappingToElasticaTest extends WebTestCase
$this->assertArrayHasKey('store', $mapping['type']['properties']['field1']);
$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();
+
+ $this->assertEquals('my_analyzer', $mapping['parent']['index_analyzer']);
+ $this->assertEquals('whitespace', $mapping['parent']['search_analyzer']);
}
public function testResetType()
@@ -43,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']);
@@ -90,6 +103,7 @@ class MappingToElasticaTest extends WebTestCase
/**
* @param Client $client
+ *
* @return \FOS\ElasticaBundle\Resetter $resetter
*/
private function getResetter(Client $client)
@@ -99,11 +113,13 @@ class MappingToElasticaTest extends WebTestCase
/**
* @param Client $client
+ * @param string $type
+ *
* @return \Elastica\Type
*/
- private function getType(Client $client)
+ private function getType(Client $client, $type = 'type')
{
- return $client->getContainer()->get('fos_elastica.index.index.type');
+ return $client->getContainer()->get('fos_elastica.index.index.'.$type);
}
protected function setUp()
diff --git a/Tests/Functional/PropertyPathTest.php b/Tests/Functional/PropertyPathTest.php
new file mode 100644
index 0000000..860cb86
--- /dev/null
+++ b/Tests/Functional/PropertyPathTest.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Functional;
+
+use Elastica\Query\Match;
+
+/**
+ * @group functional
+ */
+class PropertyPathTest extends WebTestCase
+{
+ public function testContainerSource()
+ {
+ $client = $this->createClient(array('test_case' => 'ORM'));
+ /** @var \FOS\ElasticaBundle\Persister\ObjectPersister $persister */
+ $persister = $client->getContainer()->get('fos_elastica.object_persister.index.property_paths_type');
+ $obj = new TypeObj();
+ $obj->coll = 'Hello';
+ $persister->insertOne($obj);
+
+ /** @var \Elastica\Index $elClient */
+ $index = $client->getContainer()->get('fos_elastica.index.index');
+ $index->flush(true);
+
+ $query = new Match();
+ $query->setField('something', 'Hello');
+ $search = $index->createSearch($query);
+
+ $this->assertEquals(1, $search->count());
+ }
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->deleteTmpDir('Basic');
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->deleteTmpDir('Basic');
+ }
+}
diff --git a/Tests/Functional/SerializerTest.php b/Tests/Functional/SerializerTest.php
index 3a3b8cb..81fbc8f 100644
--- a/Tests/Functional/SerializerTest.php
+++ b/Tests/Functional/SerializerTest.php
@@ -32,6 +32,13 @@ class SerializerTest extends WebTestCase
$persister->replaceOne($object);
}
+ public function testUnmappedType()
+ {
+ $client = $this->createClient(array('test_case' => 'Serializer'));
+ $resetter = $client->getContainer()->get('fos_elastica.resetter');
+ $resetter->resetIndex('index');
+ }
+
protected function setUp()
{
parent::setUp();
diff --git a/Tests/Functional/TypeObj.php b/Tests/Functional/TypeObj.php
index 46e5968..39e9fe9 100644
--- a/Tests/Functional/TypeObj.php
+++ b/Tests/Functional/TypeObj.php
@@ -13,8 +13,10 @@ namespace FOS\ElasticaBundle\Tests\Functional;
class TypeObj
{
+ public $id = 5;
public $coll;
public $field1;
+ public $field2;
public function isIndexable()
{
diff --git a/Tests/Functional/app/AppKernel.php b/Tests/Functional/app/AppKernel.php
index f47a5b3..d75910a 100644
--- a/Tests/Functional/app/AppKernel.php
+++ b/Tests/Functional/app/AppKernel.php
@@ -115,4 +115,4 @@ class AppKernel extends Kernel
return $parameters;
}
-}
\ No newline at end of file
+}
diff --git a/Tests/Functional/app/Basic/config.yml b/Tests/Functional/app/Basic/config.yml
index 607e3cc..8ed88eb 100644
--- a/Tests/Functional/app/Basic/config.yml
+++ b/Tests/Functional/app/Basic/config.yml
@@ -15,7 +15,12 @@ fos_elastica:
- url: http://localhost:9200
- host: localhost
port: 9200
+ connectionStrategy: RoundRobin
second_server:
+ connections:
+ - url: http://localhost:9200
+ connection_strategy: RoundRobin
+ third:
url: http://localhost:9200
indexes:
index:
@@ -42,8 +47,12 @@ fos_elastica:
mappings:
field1: ~
field2: ~
+ search_analyzer: whitespace
+ index_analyzer: my_analyzer
type:
search_analyzer: my_analyzer
+ date_detection: false
+ dynamic_date_formats: [ 'yyyy-MM-dd' ]
dynamic_templates:
- dates:
match: "date_*"
@@ -54,6 +63,7 @@ fos_elastica:
mapping:
analyzer: english
type: string
+ numeric_detection: true
properties:
field1: ~
field2:
@@ -69,6 +79,11 @@ fos_elastica:
properties:
date: { boost: 5 }
content: ~
+ multiple:
+ type: "multi_field"
+ properties:
+ name: ~
+ position: ~
user:
type: "object"
approver:
@@ -83,3 +98,6 @@ fos_elastica:
type: "parent"
property: "parent"
identifier: "id"
+ null_mappings:
+ mappings: ~
+ empty_index: ~
diff --git a/Tests/Functional/app/ORM/IndexableService.php b/Tests/Functional/app/ORM/IndexableService.php
index 018451e..8f17bd0 100644
--- a/Tests/Functional/app/ORM/IndexableService.php
+++ b/Tests/Functional/app/ORM/IndexableService.php
@@ -13,12 +13,12 @@ namespace FOS\ElasticaBundle\Tests\Functional\app\ORM;
class IndexableService
{
- public function isIndexable($object)
+ public function isIndexable()
{
return true;
}
- public static function isntIndexable($object)
+ public static function isntIndexable()
{
return false;
}
diff --git a/Tests/Functional/app/ORM/config.yml b/Tests/Functional/app/ORM/config.yml
index 3dc0e63..d2ff931 100644
--- a/Tests/Functional/app/ORM/config.yml
+++ b/Tests/Functional/app/ORM/config.yml
@@ -35,6 +35,8 @@ fos_elastica:
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener:
is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()'
+ provider:
+ debug_logging: true
type2:
properties:
field1: ~
@@ -63,3 +65,27 @@ 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:
+ type:
+ properties:
+ field1: ~
+ coll: ~
+ persistence:
+ driver: orm
+ model: FOS\ElasticaBundle\Tests\Functional\TypeObj
+ listener:
+ is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()'
diff --git a/Tests/Functional/app/Serializer/config.yml b/Tests/Functional/app/Serializer/config.yml
index ccd18f4..98e59e8 100644
--- a/Tests/Functional/app/Serializer/config.yml
+++ b/Tests/Functional/app/Serializer/config.yml
@@ -28,7 +28,7 @@ fos_elastica:
serializer: ~
indexes:
index:
- index_name: foselastica_test_%kernel.environment%
+ index_name: foselastica_ser_test_%kernel.environment%
types:
type:
properties:
@@ -40,3 +40,10 @@ fos_elastica:
serializer:
groups: ['search', 'Default']
version: 1.1
+ unmapped:
+ persistence:
+ driver: orm
+ model: FOS\ElasticaBundle\Tests\Functional\TypeObj
+ serializer:
+ groups: ['search', 'Default']
+ version: 1.1
diff --git a/Tests/Index/AliasProcessorTest.php b/Tests/Index/AliasProcessorTest.php
new file mode 100644
index 0000000..f1592b2
--- /dev/null
+++ b/Tests/Index/AliasProcessorTest.php
@@ -0,0 +1,223 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Tests\Index;
+
+use Elastica\Exception\ResponseException;
+use Elastica\Request;
+use Elastica\Response;
+use FOS\ElasticaBundle\Configuration\IndexConfig;
+use FOS\ElasticaBundle\Index\AliasProcessor;
+
+class AliasProcessorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var AliasProcessor
+ */
+ private $processor;
+
+ /**
+ * @dataProvider getSetRootNameData
+ * @param string $name
+ * @param array $configArray
+ * @param string $resultStartsWith
+ */
+ public function testSetRootName($name, $configArray, $resultStartsWith)
+ {
+ $indexConfig = new IndexConfig($name, array(), $configArray);
+ $index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $index->expects($this->once())
+ ->method('overrideName')
+ ->with($this->stringStartsWith($resultStartsWith));
+
+ $this->processor->setRootName($indexConfig, $index);
+ }
+
+ public function testSwitchAliasNoAliasSet()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array()));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ public function testSwitchAliasExistingAliasSet()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name'))
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testSwitchAliasThrowsWhenMoreThanOneExists()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ 'another_old_unique_name' => array('aliases' => array('name'))
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ /**
+ * @expectedException \FOS\ElasticaBundle\Exception\AliasIsIndexException
+ */
+ public function testSwitchAliasThrowsWhenAliasIsAnIndex()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'name' => array(),
+ )));
+
+ $this->processor->switchIndexAlias($indexConfig, $index, false);
+ }
+
+ public function testSwitchAliasDeletesIndexCollisionIfForced()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'name' => array(),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('name', 'DELETE');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function testSwitchAliasDeletesOldIndex()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )));
+ $client->expects($this->at(2))
+ ->method('request')
+ ->with('old_unique_name', 'DELETE');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function testSwitchAliasCleansUpOnRenameFailure()
+ {
+ $indexConfig = new IndexConfig('name', array(), array());
+ list($index, $client) = $this->getMockedIndex('unique_name');
+
+ $client->expects($this->at(0))
+ ->method('request')
+ ->with('_aliases', 'GET')
+ ->willReturn(new Response(array(
+ 'old_unique_name' => array('aliases' => array('name')),
+ )));
+ $client->expects($this->at(1))
+ ->method('request')
+ ->with('_aliases', 'POST', array('actions' => array(
+ array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
+ array('add' => array('index' => 'unique_name', 'alias' => 'name'))
+ )))
+ ->will($this->throwException(new ResponseException(new Request(''), new Response(''))));
+ $client->expects($this->at(2))
+ ->method('request')
+ ->with('unique_name', 'DELETE');
+ // Not an annotation: we do not want a RuntimeException until now.
+ $this->setExpectedException('RuntimeException');
+
+ $this->processor->switchIndexAlias($indexConfig, $index, true);
+ }
+
+ public function getSetRootNameData()
+ {
+ return array(
+ array('name', array(), 'name_'),
+ array('name', array('elasticSearchName' => 'notname'), 'notname_')
+ );
+ }
+
+ protected function setUp()
+ {
+ $this->processor = new AliasProcessor();
+ }
+
+ private function getMockedIndex($name)
+ {
+ $index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $client = $this->getMockBuilder('Elastica\\Client')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $index->expects($this->any())
+ ->method('getClient')
+ ->willReturn($client);
+
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn($name);
+
+ return array($index, $client);
+ }
+}
diff --git a/Tests/Index/IndexManagerTest.php b/Tests/Index/IndexManagerTest.php
index 98e4d8a..78a3d28 100644
--- a/Tests/Index/IndexManagerTest.php
+++ b/Tests/Index/IndexManagerTest.php
@@ -13,7 +13,6 @@ class IndexManagerTest extends \PHPUnit_Framework_TestCase
*/
private $indexManager;
-
public function setUp()
{
foreach (array('index1', 'index2', 'index3') as $indexName) {
diff --git a/Tests/Index/ResetterTest.php b/Tests/Index/ResetterTest.php
index 28f0a68..9b4cd05 100644
--- a/Tests/Index/ResetterTest.php
+++ b/Tests/Index/ResetterTest.php
@@ -5,8 +5,14 @@ namespace FOS\ElasticaBundle\Tests\Index;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
+use Elastica\Type;
use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\IndexConfig;
+use FOS\ElasticaBundle\Configuration\TypeConfig;
+use FOS\ElasticaBundle\Elastica\Index;
+use FOS\ElasticaBundle\Event\IndexResetEvent;
+use FOS\ElasticaBundle\Event\TypeResetEvent;
+use FOS\ElasticaBundle\Index\AliasProcessor;
use FOS\ElasticaBundle\Index\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase
@@ -16,227 +22,253 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
*/
private $resetter;
- private $configManager;
- private $indexManager;
private $aliasProcessor;
+ private $configManager;
+ private $dispatcher;
+ private $elasticaClient;
+ private $indexManager;
private $mappingBuilder;
- public function setUp()
+ public function testResetAllIndexes()
{
- $this->markTestIncomplete('To be rewritten');
+ $indexName = 'index1';
+ $indexConfig = new IndexConfig($indexName, array(), array());
+ $this->mockIndex($indexName, $indexConfig);
+
+ $this->configManager->expects($this->once())
+ ->method('getIndexNames')
+ ->will($this->returnValue(array($indexName)));
+
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetAllIndexes();
+ }
+
+ public function testResetIndex()
+ {
+ $indexConfig = new IndexConfig('index1', array(), array());
+ $this->mockIndex('index1', $indexConfig);
+
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
+ }
+
+ public function testResetIndexWithDifferentName()
+ {
+ $indexConfig = new IndexConfig('index1', array(), array(
+ 'elasticSearchName' => 'notIndex1'
+ ));
+ $this->mockIndex('index1', $indexConfig);
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
+ }
+
+ public function testResetIndexWithDifferentNameAndAlias()
+ {
+ $indexConfig = new IndexConfig('index1', array(), array(
+ 'elasticSearchName' => 'notIndex1',
+ 'useAlias' => true
+ ));
+ $index = $this->mockIndex('index1', $indexConfig);
+ $this->dispatcherExpects(array(
+ array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
+ array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
+ ));
+
+ $this->aliasProcessor->expects($this->once())
+ ->method('switchIndexAlias')
+ ->with($indexConfig, $index, false);
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index1/', 'DELETE'),
+ array('index1/', 'PUT', array(), array())
+ );
+
+ $this->resetter->resetIndex('index1');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testFailureWhenMissingIndexDoesntDispatch()
+ {
+ $this->configManager->expects($this->once())
+ ->method('getIndexConfiguration')
+ ->with('nonExistant')
+ ->will($this->throwException(new \InvalidArgumentException));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+
+ $this->resetter->resetIndex('nonExistant');
+ }
+
+ public function testResetType()
+ {
+ $typeConfig = new TypeConfig('type', array(), array());
+ $this->mockType('type', 'index', $typeConfig);
+
+ $this->dispatcherExpects(array(
+ array(TypeResetEvent::PRE_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent')),
+ array(TypeResetEvent::POST_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent'))
+ ));
+
+ $this->elasticaClient->expects($this->exactly(2))
+ ->method('request')
+ ->withConsecutive(
+ array('index/type/', 'DELETE'),
+ array('index/type/_mapping', 'PUT', array('type' => array()), array())
+ );
+
+ $this->resetter->resetIndexType('index', 'type');
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testNonExistantResetType()
+ {
+ $this->configManager->expects($this->once())
+ ->method('getTypeConfiguration')
+ ->with('index', 'nonExistant')
+ ->will($this->throwException(new \InvalidArgumentException));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+
+ $this->resetter->resetIndexType('index', 'nonExistant');
+ }
+
+ public function testPostPopulateWithoutAlias()
+ {
+ $this->mockIndex('index', new IndexConfig('index', array(), array()));
+
+ $this->indexManager->expects($this->never())
+ ->method('getIndex');
+ $this->aliasProcessor->expects($this->never())
+ ->method('switchIndexAlias');
+
+ $this->resetter->postPopulate('index');
+ }
+
+ public function testPostPopulate()
+ {
+ $indexConfig = new IndexConfig('index', array(), array( 'useAlias' => true));
+ $index = $this->mockIndex('index', $indexConfig);
+
+ $this->aliasProcessor->expects($this->once())
+ ->method('switchIndexAlias')
+ ->with($indexConfig, $index);
+
+ $this->resetter->postPopulate('index');
+ }
+
+ private function dispatcherExpects(array $events)
+ {
+ $expectation = $this->dispatcher->expects($this->exactly(count($events)))
+ ->method('dispatch');
+
+ call_user_func_array(array($expectation, 'withConsecutive'), $events);
+ }
+
+ private function mockIndex($indexName, IndexConfig $config, $mapping = array())
+ {
+ $this->configManager->expects($this->atLeast(1))
+ ->method('getIndexConfiguration')
+ ->with($indexName)
+ ->will($this->returnValue($config));
+ $index = new Index($this->elasticaClient, $indexName);
+ $this->indexManager->expects($this->any())
+ ->method('getIndex')
+ ->with($indexName)
+ ->willReturn($index);
+ $this->mappingBuilder->expects($this->any())
+ ->method('buildIndexMapping')
+ ->with($config)
+ ->willReturn($mapping);
+
+ return $index;
+ }
+
+ private function mockType($typeName, $indexName, TypeConfig $config, $mapping = array())
+ {
+ $this->configManager->expects($this->atLeast(1))
+ ->method('getTypeConfiguration')
+ ->with($indexName, $typeName)
+ ->will($this->returnValue($config));
+ $index = new Index($this->elasticaClient, $indexName);
+ $this->indexManager->expects($this->once())
+ ->method('getIndex')
+ ->with($indexName)
+ ->willReturn($index);
+ $this->mappingBuilder->expects($this->once())
+ ->method('buildTypeMapping')
+ ->with($config)
+ ->willReturn($mapping);
+
+ return $index;
+ }
+
+ protected function setUp()
+ {
+ $this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
+ ->disableOriginalConstructor()
+ ->getMock();
$this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
->disableOriginalConstructor()
->getMock();
- $this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
+ $this->dispatcher = $this->getMockBuilder('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface')
+ ->getMock();
+ $this->elasticaClient = $this->getMockBuilder('Elastica\\Client')
->disableOriginalConstructor()
->getMock();
- $this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
+ $this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
->disableOriginalConstructor()
->getMock();
$this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder')
->disableOriginalConstructor()
->getMock();
- $this->resetter = new Resetter($this->configManager, $this->indexManager, $this->aliasProcessor, $this->mappingBuilder);
-
- /*$this->indexConfigsByName = array(
- 'foo' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array(
- 'dynamic_templates' => array(),
- 'properties' => array(),
- ),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- 'bar' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array('properties' => array()),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- 'parent' => array(
- 'index' => $this->getMockElasticaIndex(),
- 'config' => array(
- 'properties' => array(
- 'a' => array(
- 'properties' => array(
- 'field_2' => array()
- ),
- '_parent' => array(
- 'type' => 'b',
- 'property' => 'b',
- 'identifier' => 'id'
- ),
- ),
- 'b' => array('properties' => array()),
- ),
- ),
- ),
- );*/
- }
-
- public function testResetAllIndexes()
- {
- $this->configManager->expects($this->once())
- ->method('getIndexNames')
- ->will($this->returnValue(array('index1')));
-
- $this->configManager->expects($this->once())
- ->method('getIndexConfiguration')
- ->with('index1')
- ->will($this->returnValue(new IndexConfig('index1', array(), array())));
-
- $this->indexManager->expects($this->once())
- ->method('getIndex')
- ->with('index1')
- ->will($this->returnValue());
-
- /*$this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('create')
- ->with($this->indexConfigsByName['foo']['config'], true);
-
- $this->indexConfigsByName['bar']['index']->expects($this->once())
- ->method('create')
- ->with($this->indexConfigsByName['bar']['config'], true);
-
- $resetter = new Resetter($this->indexConfigsByName);*/
- $this->resetter->resetAllIndexes();
- }
-
- public function testResetIndex()
- {
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('create')
- ->with($this->indexConfigsByName['foo']['config'], true);
-
- $this->indexConfigsByName['bar']['index']->expects($this->never())
- ->method('create');
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndex('foo');
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testResetIndexShouldThrowExceptionForInvalidIndex()
- {
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndex('baz');
- }
-
- public function testResetIndexType()
- {
- $type = $this->getMockElasticaType();
-
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
-
- $type->expects($this->once())
- ->method('delete');
-
- $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
- $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'a');
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testResetIndexTypeShouldThrowExceptionForInvalidIndex()
- {
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('baz', 'a');
- }
-
- /**
- * @expectedException \InvalidArgumentException
- */
- public function testResetIndexTypeShouldThrowExceptionForInvalidType()
- {
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'c');
- }
-
- public function testResetIndexTypeIgnoreTypeMissingException()
- {
- $type = $this->getMockElasticaType();
-
- $this->indexConfigsByName['foo']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
-
- $type->expects($this->once())
- ->method('delete')
- ->will($this->throwException(new ResponseException(
- new Request(''),
- new Response(array('error' => 'TypeMissingException[[de_20131022] type[bla] missing]', 'status' => 404)))
- ));
-
- $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
- $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('foo', 'a');
- }
-
- public function testIndexMappingForParent()
- {
- $type = $this->getMockElasticaType();
-
- $this->indexConfigsByName['parent']['index']->expects($this->once())
- ->method('getType')
- ->with('a')
- ->will($this->returnValue($type));
-
- $type->expects($this->once())
- ->method('delete');
-
- $mapping = Mapping::create($this->indexConfigsByName['parent']['config']['properties']['a']['properties']);
- $mapping->setParam('_parent', array('type' => 'b'));
- $type->expects($this->once())
- ->method('setMapping')
- ->with($mapping);
-
- $resetter = new Resetter($this->indexConfigsByName);
- $resetter->resetIndexType('parent', 'a');
- }
-
- /**
- * @return \Elastica\Index
- */
- private function getMockElasticaIndex()
- {
- return $this->getMockBuilder('Elastica\Index')
- ->disableOriginalConstructor()
- ->getMock();
- }
-
- /**
- * @return \Elastica\Type
- */
- private function getMockElasticaType()
- {
- return $this->getMockBuilder('Elastica\Type')
- ->disableOriginalConstructor()
- ->getMock();
+ $this->resetter = new Resetter(
+ $this->configManager,
+ $this->indexManager,
+ $this->aliasProcessor,
+ $this->mappingBuilder,
+ $this->dispatcher
+ );
}
}
diff --git a/Tests/Integration/MappingTest.php b/Tests/Integration/MappingTest.php
index be134ed..ae7e409 100644
--- a/Tests/Integration/MappingTest.php
+++ b/Tests/Integration/MappingTest.php
@@ -8,10 +8,8 @@
* with this source code in the file LICENSE.
*/
-
namespace FOS\ElasticaBundle\Tests\Integration;
-
-class MappingTest {
-
-}
\ No newline at end of file
+class MappingTest
+{
+}
diff --git a/Tests/Logger/ElasticaLoggerTest.php b/Tests/Logger/ElasticaLoggerTest.php
index 96adf53..7d90639 100644
--- a/Tests/Logger/ElasticaLoggerTest.php
+++ b/Tests/Logger/ElasticaLoggerTest.php
@@ -22,7 +22,8 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
/**
* @param string $level
* @param string $message
- * @param array $context
+ * @param array $context
+ *
* @return ElasticaLogger
*/
private function getMockLoggerForLevelMessageAndContext($level, $message, $context)
@@ -45,7 +46,7 @@ class ElasticaLoggerTest extends \PHPUnit_Framework_TestCase
public function testGetZeroIfNoQueriesAdded()
{
- $elasticaLogger = new ElasticaLogger;
+ $elasticaLogger = new ElasticaLogger();
$this->assertEquals(0, $elasticaLogger->getNbQueries());
}
diff --git a/Tests/Manager/RepositoryManagerTest.php b/Tests/Manager/RepositoryManagerTest.php
index 8849035..71bb076 100644
--- a/Tests/Manager/RepositoryManagerTest.php
+++ b/Tests/Manager/RepositoryManagerTest.php
@@ -4,16 +4,19 @@ namespace FOS\ElasticaBundle\Tests\Manager;
use FOS\ElasticaBundle\Manager\RepositoryManager;
-class CustomRepository{}
+class CustomRepository
+{
+}
-class Entity{}
+class Entity
+{
+}
/**
* @author Richard Miller
*/
class RepositoryManagerTest extends \PHPUnit_Framework_TestCase
{
-
public function testThatGetRepositoryReturnsDefaultRepository()
{
/** @var $finderMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
diff --git a/Tests/Persister/ObjectPersisterTest.php b/Tests/Persister/ObjectPersisterTest.php
index 77a8809..06039f0 100644
--- a/Tests/Persister/ObjectPersisterTest.php
+++ b/Tests/Persister/ObjectPersisterTest.php
@@ -31,13 +31,6 @@ class InvalidObjectPersister extends ObjectPersister
class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
{
- public function setUp()
- {
- if (!class_exists('Elastica\Type')) {
- $this->markTestSkipped('The Elastica library classes are not available');
- }
- }
-
public function testThatCanReplaceObject()
{
$transformer = $this->getTransformer();
@@ -210,7 +203,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer()
{
$transformer = new ModelToElasticaAutoTransformer();
- $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
+ $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}
diff --git a/Tests/Persister/ObjectSerializerPersisterTest.php b/Tests/Persister/ObjectSerializerPersisterTest.php
index fe15c0c..0536d06 100644
--- a/Tests/Persister/ObjectSerializerPersisterTest.php
+++ b/Tests/Persister/ObjectSerializerPersisterTest.php
@@ -2,9 +2,7 @@
namespace FOS\ElasticaBundle\Tests\ObjectSerializerPersister;
-use FOS\ElasticaBundle\Persister\ObjectPersister;
use FOS\ElasticaBundle\Persister\ObjectSerializerPersister;
-use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use FOS\ElasticaBundle\Transformer\ModelToElasticaIdentifierTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
@@ -26,13 +24,6 @@ class POPO
class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase
{
- public function setUp()
- {
- if (!class_exists('Elastica\Type')) {
- $this->markTestSkipped('The Elastica library classes are not available');
- }
- }
-
public function testThatCanReplaceObject()
{
$transformer = $this->getTransformer();
@@ -121,7 +112,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer()
{
$transformer = new ModelToElasticaIdentifierTransformer();
- $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
+ $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}
diff --git a/Tests/Provider/IndexableTest.php b/Tests/Provider/IndexableTest.php
index 6ef5669..e122ec1 100644
--- a/Tests/Provider/IndexableTest.php
+++ b/Tests/Provider/IndexableTest.php
@@ -21,7 +21,7 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
public function testIndexableUnknown()
{
$indexable = new Indexable(array(), $this->container);
- $index = $indexable->isObjectIndexable('index', 'type', new Entity);
+ $index = $indexable->isObjectIndexable('index', 'type', new Entity());
$this->assertTrue($index);
}
@@ -32,9 +32,9 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
public function testValidIndexableCallbacks($callback, $return)
{
$indexable = new Indexable(array(
- 'index/type' => $callback
+ 'index/type' => $callback,
), $this->container);
- $index = $indexable->isObjectIndexable('index', 'type', new Entity);
+ $index = $indexable->isObjectIndexable('index', 'type', new Entity());
$this->assertEquals($return, $index);
}
@@ -46,15 +46,16 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
public function testInvalidIsIndexableCallbacks($callback)
{
$indexable = new Indexable(array(
- 'index/type' => $callback
+ 'index/type' => $callback,
), $this->container);
- $indexable->isObjectIndexable('index', 'type', new Entity);
+ $indexable->isObjectIndexable('index', 'type', new Entity());
}
public function provideInvalidIsIndexableCallbacks()
{
return array(
array('nonexistentEntityMethod'),
+ array(array('@indexableService', 'internalMethod')),
array(array(new IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
@@ -67,10 +68,13 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
array('isIndexable', false),
array(array(new IndexableDecider(), 'isIndexable'), true),
array(array('@indexableService', 'isIndexable'), true),
- array(function(Entity $entity) { return $entity->maybeIndex(); }, true),
+ array(array('@indexableService'), true),
+ array(function (Entity $entity) { return $entity->maybeIndex(); }, true),
array('entity.maybeIndex()', true),
array('!object.isIndexable() && entity.property == "abc"', true),
array('entity.property != "abc"', false),
+ array('["array", "values"]', true),
+ array('[]', false)
);
}
@@ -111,4 +115,9 @@ class IndexableDecider
protected function internalMethod()
{
}
+
+ public function __invoke($object)
+ {
+ return true;
+ }
}
diff --git a/Tests/RepositoryTest.php b/Tests/RepositoryTest.php
index c4d4efc..7702af2 100644
--- a/Tests/RepositoryTest.php
+++ b/Tests/RepositoryTest.php
@@ -13,14 +13,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
{
$testQuery = 'Test Query';
- /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
- $finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
- ->disableOriginalConstructor()
- ->getMock();
- $finderMock->expects($this->once())
- ->method('find')
- ->with($this->equalTo($testQuery));
-
+ $finderMock = $this->getFinderMock($testQuery);
$repository = new Repository($finderMock);
$repository->find($testQuery);
}
@@ -30,14 +23,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
$testQuery = 'Test Query';
$testLimit = 20;
- /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
- $finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
- ->disableOriginalConstructor()
- ->getMock();
- $finderMock->expects($this->once())
- ->method('find')
- ->with($this->equalTo($testQuery), $this->equalTo($testLimit));
-
+ $finderMock = $this->getFinderMock($testQuery, $testLimit);
$repository = new Repository($finderMock);
$repository->find($testQuery, $testLimit);
}
@@ -46,14 +32,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
{
$testQuery = 'Test Query';
- /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
- $finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
- ->disableOriginalConstructor()
- ->getMock();
- $finderMock->expects($this->once())
- ->method('findPaginated')
- ->with($this->equalTo($testQuery));
-
+ $finderMock = $this->getFinderMock($testQuery, array(), 'findPaginated');
$repository = new Repository($finderMock);
$repository->findPaginated($testQuery);
}
@@ -62,14 +41,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
{
$testQuery = 'Test Query';
- /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
- $finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
- ->disableOriginalConstructor()
- ->getMock();
- $finderMock->expects($this->once())
- ->method('createPaginatorAdapter')
- ->with($this->equalTo($testQuery));
-
+ $finderMock = $this->getFinderMock($testQuery, array(), 'createPaginatorAdapter');
$repository = new Repository($finderMock);
$repository->createPaginatorAdapter($testQuery);
}
@@ -77,17 +49,28 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
public function testThatFindHybridCallsFindHybridOnFinder()
{
$testQuery = 'Test Query';
- $testLimit = 20;
- /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */
+ $finderMock = $this->getFinderMock($testQuery, null, 'findHybrid');
+ $repository = new Repository($finderMock);
+ $repository->findHybrid($testQuery);
+ }
+
+ /**
+ * @param string $testQuery
+ * @param mixed $testLimit
+ * @param string $method
+ *
+ * @return \FOS\ElasticaBundle\Finder\TransformedFinder
+ */
+ private function getFinderMock($testQuery, $testLimit = null, $method = 'find')
+ {
$finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder')
->disableOriginalConstructor()
->getMock();
$finderMock->expects($this->once())
- ->method('findHybrid')
+ ->method($method)
->with($this->equalTo($testQuery), $this->equalTo($testLimit));
- $repository = new Repository($finderMock);
- $repository->findHybrid($testQuery, $testLimit);
+ return $finderMock;
}
}
diff --git a/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php b/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php
index eb4d8e4..56a7200 100644
--- a/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php
+++ b/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php
@@ -37,7 +37,7 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$this->collection = new ElasticaToModelTransformerCollection($this->transformers = array(
'type1' => $transformer1,
'type2' => $transformer2,
- ), array());
+ ));
}
public function testGetObjectClass()
@@ -47,7 +47,7 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$objectClasses = $this->collection->getObjectClass();
$this->assertEquals(array(
'type1' => 'FOS\ElasticaBundle\Tests\Transformer\POPO',
- 'type2' => 'FOS\ElasticaBundle\Tests\Transformer\POPO2'
+ 'type2' => 'FOS\ElasticaBundle\Tests\Transformer\POPO2',
), $objectClasses);
}
@@ -89,8 +89,8 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$this->transformers['type1']->expects($this->once())
->method('transform')
- ->with(array($document1,$document2))
- ->will($this->returnValue(array($result1,$result2)));
+ ->with(array($document1, $document2))
+ ->will($this->returnValue(array($result1, $result2)));
$results = $this->collection->transform(array($document1, $document2));
@@ -120,8 +120,8 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
return array(
array(
- $result, $transformedObject
- )
+ $result, $transformedObject,
+ ),
);
}
@@ -157,6 +157,9 @@ class POPO
public $id;
public $data;
+ /**
+ * @param integer $id
+ */
public function __construct($id, $data)
{
$this->data = $data;
diff --git a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
index 1fa6a8e..f45134e 100644
--- a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
+++ b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php
@@ -2,6 +2,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer;
+use FOS\ElasticaBundle\Event\TransformEvent;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
@@ -21,8 +22,8 @@ class POPO
public function __construct()
{
$this->date = new \DateTime('1979-05-05');
- $this->file = new \SplFileInfo(__DIR__ . '/../fixtures/attachment.odt');
- $this->fileContents = file_get_contents(__DIR__ . '/../fixtures/attachment.odt');
+ $this->file = new \SplFileInfo(__DIR__.'/../fixtures/attachment.odt');
+ $this->fileContents = file_get_contents(__DIR__.'/../fixtures/attachment.odt');
}
public function getId()
@@ -47,7 +48,7 @@ class POPO
{
return array(
'key1' => 'value1',
- 'key2' => 'value2'
+ 'key2' => 'value2',
);
}
@@ -109,7 +110,7 @@ class POPO
public function getNestedObject()
{
- return array('key1' => (object)array('id' => 1, 'key1sub1' => 'value1sub1', 'key1sub2' => 'value1sub2'));
+ return array('key1' => (object) array('id' => 1, 'key1sub1' => 'value1sub1', 'key1sub2' => 'value1sub2'));
}
public function getUpper()
@@ -125,11 +126,33 @@ class POPO
class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
- public function setUp()
+ public function testTransformerDispatches()
{
- if (!class_exists('Elastica\Document')) {
- $this->markTestSkipped('The Elastica library classes are not available');
- }
+ $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()
@@ -152,7 +175,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
'float' => array(),
'bool' => array(),
'date' => array(),
- 'falseBool' => array()
+ 'falseBool' => array(),
)
);
$data = $document->getData();
@@ -185,7 +208,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(
array(
'key1' => 'value1',
- 'key2' => 'value2'
+ 'key2' => 'value2',
), $data['array']
);
}
@@ -230,7 +253,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$document = $transformer->transform(new POPO(), array('file' => array('type' => 'attachment')));
$data = $document->getData();
- $this->assertEquals(base64_encode(file_get_contents(__DIR__ . '/../fixtures/attachment.odt')), $data['file']);
+ $this->assertEquals(base64_encode(file_get_contents(__DIR__.'/../fixtures/attachment.odt')), $data['file']);
}
public function testFileContentsAddedForAttachmentMapping()
@@ -240,7 +263,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$data = $document->getData();
$this->assertEquals(
- base64_encode(file_get_contents(__DIR__ . '/../fixtures/attachment.odt')), $data['fileContents']
+ base64_encode(file_get_contents(__DIR__.'/../fixtures/attachment.odt')), $data['fileContents']
);
}
@@ -248,18 +271,18 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
- 'sub' => array(
- 'type' => 'nested',
- 'properties' => array('foo' => '~')
- )
- ));
+ 'sub' => array(
+ 'type' => 'nested',
+ 'properties' => array('foo' => array()),
+ ),
+ ));
$data = $document->getData();
$this->assertTrue(array_key_exists('sub', $data));
$this->assertInternalType('array', $data['sub']);
$this->assertEquals(array(
array('foo' => 'foo'),
- array('foo' => 'bar')
+ array('foo' => 'bar'),
), $data['sub']);
}
@@ -269,8 +292,8 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$document = $transformer->transform(new POPO(), array(
'sub' => array(
'type' => 'object',
- 'properties' => array('bar')
- )
+ 'properties' => array('bar'),
+ ),
));
$data = $document->getData();
@@ -278,7 +301,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$this->assertInternalType('array', $data['sub']);
$this->assertEquals(array(
array('bar' => 'foo'),
- array('bar' => 'bar')
+ array('bar' => 'bar'),
), $data['sub']);
}
@@ -287,18 +310,18 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'obj' => array(
- 'type' => 'object'
- )
+ 'type' => 'object',
+ ),
));
$data = $document->getData();
$this->assertTrue(array_key_exists('obj', $data));
$this->assertInternalType('array', $data['obj']);
$this->assertEquals(array(
- 'foo' => 'foo',
- 'bar' => 'foo',
- 'id' => 1
- ), $data['obj']);
+ 'foo' => 'foo',
+ 'bar' => 'foo',
+ 'id' => 1,
+ ), $data['obj']);
}
public function testObjectsMappingOfAtLeastOneAutoMappedObjectAndAtLeastOneManuallyMappedObject()
@@ -313,14 +336,14 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
'properties' => array(
'key1sub1' => array(
'type' => 'string',
- 'properties' => array()
+ 'properties' => array(),
),
'key1sub2' => array(
'type' => 'string',
- 'properties' => array()
- )
- )
- )
+ 'properties' => array(),
+ ),
+ ),
+ ),
)
);
$data = $document->getData();
@@ -333,14 +356,14 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
array(
'foo' => 'foo',
'bar' => 'foo',
- 'id' => 1
+ 'id' => 1,
),
$data['obj']
);
$this->assertEquals(
array(
'key1sub1' => 'value1sub1',
- 'key1sub2' => 'value1sub2'
+ 'key1sub2' => 'value1sub2',
),
$data['nestedObject'][0]
);
@@ -350,7 +373,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
- '_parent' => array('type' => 'upper', 'property'=>'upper', 'identifier' => 'id'),
+ '_parent' => array('type' => 'upper', 'property' => 'upper', 'identifier' => 'id'),
));
$this->assertEquals("parent", $document->getParent());
@@ -360,7 +383,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
- '_parent' => array('type' => 'upper', 'property'=>'upper', 'identifier' => 'name'),
+ '_parent' => array('type' => 'upper', 'property' => 'upper', 'identifier' => 'name'),
));
$this->assertEquals("a random name", $document->getParent());
@@ -370,7 +393,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
- '_parent' => array('type' => 'upper', 'property'=>null, 'identifier' => 'id'),
+ '_parent' => array('type' => 'upper', 'property' => null, 'identifier' => 'id'),
));
$this->assertEquals("parent", $document->getParent());
@@ -380,19 +403,21 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
- '_parent' => array('type' => 'upper', 'property'=>'upperAlias', 'identifier' => 'id'),
+ '_parent' => array('type' => 'upper', 'property' => 'upperAlias', 'identifier' => 'id'),
));
$this->assertEquals("parent", $document->getParent());
}
/**
+ * @param null|\Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
+ *
* @return ModelToElasticaAutoTransformer
*/
- private function getTransformer()
+ private function getTransformer($dispatcher = null)
{
- $transformer = new ModelToElasticaAutoTransformer();
- $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
+ $transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher);
+ $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}
diff --git a/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php b/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php
index f1a77d4..aa3d7b7 100644
--- a/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php
+++ b/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php
@@ -23,13 +23,6 @@ class POPO
class ModelToElasticaIdentifierTransformerTest extends \PHPUnit_Framework_TestCase
{
- public function setUp()
- {
- if (!class_exists('Elastica\Document')) {
- $this->markTestSkipped('The Elastica library classes are not available');
- }
- }
-
public function testGetDocumentWithIdentifierOnly()
{
$transformer = $this->getTransformer();
@@ -58,7 +51,7 @@ class ModelToElasticaIdentifierTransformerTest extends \PHPUnit_Framework_TestCa
private function getTransformer()
{
$transformer = new ModelToElasticaIdentifierTransformer();
- $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
+ $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}
diff --git a/Transformer/AbstractElasticaToModelTransformer.php b/Transformer/AbstractElasticaToModelTransformer.php
new file mode 100644
index 0000000..2b1de5c
--- /dev/null
+++ b/Transformer/AbstractElasticaToModelTransformer.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace FOS\ElasticaBundle\Transformer;
+
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+
+abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
+{
+ /**
+ * PropertyAccessor instance.
+ *
+ * @var PropertyAccessorInterface
+ */
+ protected $propertyAccessor;
+
+ /**
+ * Set the PropertyAccessor instance.
+ *
+ * @param PropertyAccessorInterface $propertyAccessor
+ */
+ public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
+ {
+ $this->propertyAccessor = $propertyAccessor;
+ }
+
+ /**
+ * Returns a sorting closure to be used with usort() to put retrieved objects
+ * back in the order that they were returned by ElasticSearch.
+ *
+ * @param array $idPos
+ * @param string $identifierPath
+ * @return callable
+ */
+ protected function getSortingClosure(array $idPos, $identifierPath)
+ {
+ $propertyAccessor = $this->propertyAccessor;
+
+ return function ($a, $b) use ($idPos, $identifierPath, $propertyAccessor) {
+ return $idPos[$propertyAccessor->getValue($a, $identifierPath)] > $idPos[$propertyAccessor->getValue($b, $identifierPath)];
+ };
+ }
+}
diff --git a/Transformer/ElasticaToModelTransformerCollection.php b/Transformer/ElasticaToModelTransformerCollection.php
index f65f8db..9920f43 100644
--- a/Transformer/ElasticaToModelTransformerCollection.php
+++ b/Transformer/ElasticaToModelTransformerCollection.php
@@ -41,6 +41,7 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
/**
* @param Document[] $elasticaObjects
+ *
* @return array
*/
public function transform(array $elasticaObjects)
@@ -51,12 +52,12 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
}
$transformed = array();
- foreach ($sorted AS $type => $objects) {
+ foreach ($sorted as $type => $objects) {
$transformedObjects = $this->transformers[$type]->transform($objects);
- $identifierGetter = 'get' . ucfirst($this->transformers[$type]->getIdentifierField());
+ $identifierGetter = 'get'.ucfirst($this->transformers[$type]->getIdentifierField());
$transformed[$type] = array_combine(
array_map(
- function($o) use ($identifierGetter) {
+ function ($o) use ($identifierGetter) {
return $o->$identifierGetter();
},
$transformedObjects
@@ -80,7 +81,7 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
$objects = $this->transform($elasticaObjects);
$result = array();
- for ($i = 0; $i < count($elasticaObjects); $i++) {
+ for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
}
diff --git a/Transformer/ElasticaToModelTransformerInterface.php b/Transformer/ElasticaToModelTransformerInterface.php
index 5635ef3..71cd651 100644
--- a/Transformer/ElasticaToModelTransformerInterface.php
+++ b/Transformer/ElasticaToModelTransformerInterface.php
@@ -3,32 +3,33 @@
namespace FOS\ElasticaBundle\Transformer;
/**
- * Maps Elastica documents with model objects
+ * Maps Elastica documents with model objects.
*/
interface ElasticaToModelTransformerInterface
{
/**
* Transforms an array of elastica objects into an array of
- * model objects fetched from the doctrine repository
+ * model objects fetched from the doctrine repository.
*
* @param array $elasticaObjects array of elastica objects
+ *
* @return array of model objects
**/
- function transform(array $elasticaObjects);
+ public function transform(array $elasticaObjects);
- function hybridTransform(array $elasticaObjects);
+ public function hybridTransform(array $elasticaObjects);
/**
* Returns the object class used by the transformer.
*
* @return string
*/
- function getObjectClass();
+ public function getObjectClass();
/**
- * Returns the identifier field from the options
+ * Returns the identifier field from the options.
*
* @return string the identifier field
*/
- function getIdentifierField();
+ public function getIdentifierField();
}
diff --git a/Transformer/HighlightableModelInterface.php b/Transformer/HighlightableModelInterface.php
index d55407e..96c6c7c 100644
--- a/Transformer/HighlightableModelInterface.php
+++ b/Transformer/HighlightableModelInterface.php
@@ -1,16 +1,23 @@
- 'id'
+ 'identifier' => 'id',
);
/**
- * PropertyAccessor instance
+ * PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
- * 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->dispatcher = $dispatcher;
}
/**
- * Set the PropertyAccessor
+ * Set the PropertyAccessor.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
@@ -49,7 +58,7 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
}
/**
- * Transforms an object into an elastica object having the required keys
+ * Transforms an object into an elastica object having the required keys.
*
* @param object $object the object to convert
* @param array $fields the keys we want to have in the returned array
@@ -63,19 +72,27 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
foreach ($fields as $key => $mapping) {
if ($key == '_parent') {
- $property = (null !== $mapping['property'])?$mapping['property']:$mapping['type'];
+ $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,20 +103,28 @@ 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;
}
/**
- * transform a nested document or an object property into an array of ElasticaDocument
+ * transform a nested document or an object property into an array of ElasticaDocument.
*
* @param array|\Traversable|\ArrayAccess $objects the object to convert
- * @param array $fields the keys we want to have in the returned array
+ * @param array $fields the keys we want to have in the returned array
*
* @return array
*/
@@ -123,7 +148,7 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
}
/**
- * Attempts to convert any type to a string or an array of strings
+ * Attempts to convert any type to a string or an array of strings.
*
* @param mixed $value
*
@@ -131,12 +156,11 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
*/
protected function normalizeValue($value)
{
- $normalizeValue = function(&$v)
- {
+ $normalizeValue = function (&$v) {
if ($v instanceof \DateTime) {
$v = $v->format('c');
} elseif (!is_scalar($v) && !is_null($v)) {
- $v = (string)$v;
+ $v = (string) $v;
}
};
diff --git a/Transformer/ModelToElasticaIdentifierTransformer.php b/Transformer/ModelToElasticaIdentifierTransformer.php
index 7cf97e6..6301be1 100644
--- a/Transformer/ModelToElasticaIdentifierTransformer.php
+++ b/Transformer/ModelToElasticaIdentifierTransformer.php
@@ -6,12 +6,12 @@ use Elastica\Document;
/**
* Creates an Elastica document with the ID of
- * the Doctrine object as Elastica document ID
+ * the Doctrine object as Elastica document ID.
*/
class ModelToElasticaIdentifierTransformer extends ModelToElasticaAutoTransformer
{
/**
- * Creates an elastica document with the id of the doctrine object as id
+ * Creates an elastica document with the id of the doctrine object as id.
*
* @param object $object the object to convert
* @param array $fields the keys we want to have in the returned array
diff --git a/Transformer/ModelToElasticaTransformerInterface.php b/Transformer/ModelToElasticaTransformerInterface.php
index ec9ada3..0ad9f12 100644
--- a/Transformer/ModelToElasticaTransformerInterface.php
+++ b/Transformer/ModelToElasticaTransformerInterface.php
@@ -3,16 +3,17 @@
namespace FOS\ElasticaBundle\Transformer;
/**
- * Maps Elastica documents with model objects
+ * Maps Elastica documents with model objects.
*/
interface ModelToElasticaTransformerInterface
{
/**
- * Transforms an object into an elastica object having the required keys
+ * Transforms an object into an elastica object having the required keys.
*
* @param object $object the object to convert
- * @param array $fields the keys we want to have in the returned array
+ * @param array $fields the keys we want to have in the returned array
+ *
* @return \Elastica\Document
**/
- function transform($object, array $fields);
+ public function transform($object, array $fields);
}
diff --git a/composer.json b/composer.json
index 372320f..9705a04 100644
--- a/composer.json
+++ b/composer.json
@@ -17,37 +17,28 @@
"symfony/console": "~2.1",
"symfony/form": "~2.1",
"symfony/property-access": "~2.2",
- "ruflin/elastica": ">=0.90.10.0, <1.3-dev",
+ "ruflin/elastica": ">=0.90.10.0, <1.5-dev",
"psr/log": "~1.0"
},
"require-dev":{
- "doctrine/orm": "~2.2",
- "doctrine/doctrine-bundle": "~1.2@beta",
- "doctrine/mongodb-odm": "1.0.*@beta",
+ "doctrine/orm": "~2.4",
+ "doctrine/doctrine-bundle": "~1.2",
"jms/serializer-bundle": "@stable",
"phpunit/phpunit": "~4.1",
"propel/propel1": "1.6.*",
- "pagerfanta/pagerfanta": "1.0.*@dev",
+ "pagerfanta/pagerfanta": "~1.0",
"knplabs/knp-components": "~1.2",
"knplabs/knp-paginator-bundle": "~2.4",
"symfony/browser-kit" : "~2.3",
"symfony/expression-language" : "~2.4",
"symfony/twig-bundle": "~2.3"
},
- "suggest": {
- "doctrine/orm": "~2.2",
- "doctrine/mongodb-odm": "1.0.*@dev",
- "propel/propel1": "1.6.*",
- "pagerfanta/pagerfanta": "1.0.*@dev",
- "knplabs/knp-components": "~1.2",
- "symfony/expression-language" : "~2.4"
- },
"autoload": {
"psr-4": { "FOS\\ElasticaBundle\\": "" }
},
"extra": {
"branch-alias": {
- "dev-master": "3.0.x-dev"
+ "dev-master": "3.2.x-dev"
}
}
}