diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index e2cb043..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,5 +0,0 @@ -imports: - - php - -tools: - external_code_coverage: true diff --git a/.travis.yml b/.travis.yml index fbb22d1..8f6a9d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,9 @@ 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 - - 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;' + - echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - composer install --dev --prefer-source - -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/Annotation/Search.php b/Annotation/Search.php deleted file mode 100644 index 26e1dbf..0000000 --- a/Annotation/Search.php +++ /dev/null @@ -1,16 +0,0 @@ - - * @Annotation - * @Target("CLASS") - */ -class Search -{ - /** @var string */ - public $repositoryClass; -} diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md index 57ca45c..e57fb4d 100644 --- a/CHANGELOG-3.0.md +++ b/CHANGELOG-3.0.md @@ -12,52 +12,17 @@ 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) +* 3.0.0 - * 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 - `indexable_callback` which is run when both populating and listening for object - changes. - * AbstractProvider constructor change: Second argument is now an `IndexableInterface` - instance. - * Annotation @Search moved to FOS\ElasticaBundle\Annotation\Search with FOS\ElasticaBundle\Configuration\Search deprecated - * Deprecated FOS\ElasticaBundle\Client in favour of FOS\ElasticaBundle\Elastica\Client - * Deprecated FOS\ElasticaBundle\DynamicIndex in favour of FOS\ElasticaBundle\Elastica\Index + * Deprecated FOS\ElasticaBundle\Client in favour of FOS\ElasticaBundle\Elastica\LoggingClient + * Deprecated FOS\ElasticaBundle\DynamicIndex in favour of FOS\ElasticaBundle\Elastica\TransformingIndex * Deprecated FOS\ElasticaBundle\IndexManager in favour of FOS\ElasticaBundle\Index\IndexManager * Deprecated FOS\ElasticaBundle\Resetter in favour of FOS\ElasticaBundle\Index\Resetter -* 3.0.0-ALPHA5 (2014-05-23) - - * Doctrine Provider speed up by disabling persistence logging while populating documents - * 3.0.0-ALPHA4 (2014-04-10) * Indexes are now capable of logging errors with Elastica * Fixed deferred indexing of deleted documents - * Resetting an index will now create it even if it doesn't exist - * Bulk upserting of documents is now supported when populating * 3.0.0-ALPHA3 (2014-04-01) @@ -66,7 +31,6 @@ To generate a changelog summary since the last version, run * #415: BC BREAK: document indexing occurs in postFlush rather than the pre* events previously. * 7d13823: Dropped (broken) support for Symfony <2.3 * #496: Added support for HTTP headers - * #528: FOSElasticaBundle will disable Doctrine logging when populating for a large increase in speed * 3.0.0-ALPHA2 (2014-03-17) diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md deleted file mode 100644 index 3ca3c77..0000000 --- a/CHANGELOG-3.1.md +++ /dev/null @@ -1,61 +0,0 @@ -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/Client.php b/Client.php index d0cee46..f85756d 100644 --- a/Client.php +++ b/Client.php @@ -2,11 +2,11 @@ namespace FOS\ElasticaBundle; -use FOS\ElasticaBundle\Elastica\Client as BaseClient; +use FOS\ElasticaBundle\Elastica\LoggingClient; /** * @deprecated Use \FOS\ElasticaBundle\Elastica\LoggingClient */ -class Client extends BaseClient +class Client extends LoggingClient { } diff --git a/Command/PopulateCommand.php b/Command/PopulateCommand.php index 42af355..af5fd5d 100644 --- a/Command/PopulateCommand.php +++ b/Command/PopulateCommand.php @@ -2,8 +2,6 @@ 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; @@ -12,28 +10,18 @@ use Symfony\Component\Console\Output\OutputInterface; use FOS\ElasticaBundle\IndexManager; use FOS\ElasticaBundle\Provider\ProviderRegistry; use FOS\ElasticaBundle\Resetter; -use Symfony\Component\Console\Helper\ProgressBar; +use FOS\ElasticaBundle\Provider\ProviderInterface; /** - * 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 */ @@ -58,46 +46,31 @@ 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') ; } /** - * {@inheritDoc} + * @see Symfony\Component\Console\Command\Command::initialize() */ 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%"); - } } /** - * {@inheritDoc} + * @see Symfony\Component\Console\Command\Command::execute() */ protected function execute(InputInterface $input, OutputInterface $output) { - $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'); - } + $index = $input->getOption('index'); + $type = $input->getOption('type'); + $reset = !$input->getOption('no-reset'); + $options = $input->getOptions(); + + $options['ignore-errors'] = $input->hasOption('ignore-errors'); if ($input->isInteractive() && $reset && $input->getOption('offset')) { /** @var DialogHelper $dialog */ @@ -136,22 +109,25 @@ class PopulateCommand extends ContainerAwareCommand */ private function populateIndex(OutputInterface $output, $index, $reset, $options) { - $event = new IndexPopulateEvent($index, $reset, $options); - $this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event); - - if ($event->isReset()) { + if ($reset) { $output->writeln(sprintf('Resetting %s', $index)); - $this->resetter->resetIndex($index, true); + $this->resetter->resetIndex($index); } - $types = array_keys($this->providerRegistry->getIndexProviders($index)); - foreach ($types as $type) { - $this->populateIndexType($output, $index, $type, false, $event->getOptions()); + /** @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); } - $this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event); - - $this->refreshIndex($output, $index); + $output->writeln(sprintf('Refreshing %s', $index)); + $this->resetter->postPopulate($index); + $this->indexManager->getIndex($index)->refresh(); } /** @@ -165,35 +141,17 @@ class PopulateCommand extends ContainerAwareCommand */ private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options) { - $event = new TypePopulateEvent($index, $type, $reset, $options); - $this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event); - - if ($event->isReset()) { + if ($reset) { $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); - $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); - } + $provider->populate($loggerClosure, $options); $output->writeln(sprintf('Refreshing %s', $index)); $this->indexManager->getIndex($index)->refresh(); diff --git a/Command/ProgressClosureBuilder.php b/Command/ProgressClosureBuilder.php deleted file mode 100644 index f244bc3..0000000 --- a/Command/ProgressClosureBuilder.php +++ /dev/null @@ -1,103 +0,0 @@ - - * - * 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 85c5483..06cfe48 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,7 +33,6 @@ 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') ; } @@ -52,9 +51,8 @@ class ResetCommand extends ContainerAwareCommand */ protected function execute(InputInterface $input, OutputInterface $output) { - $index = $input->getOption('index'); - $type = $input->getOption('type'); - $force = (bool) $input->getOption('force'); + $index = $input->getOption('index'); + $type = $input->getOption('type'); if (null === $index && null !== $type) { throw new \InvalidArgumentException('Cannot specify type option without an index.'); @@ -71,7 +69,7 @@ class ResetCommand extends ContainerAwareCommand foreach ($indexes as $index) { $output->writeln(sprintf('Resetting %s', $index)); - $this->resetter->resetIndex($index, false, $force); + $this->resetter->resetIndex($index); } } } diff --git a/Command/SearchCommand.php b/Command/SearchCommand.php index 11183de..ec7cfb7 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/ConfigManager.php b/Configuration/ConfigManager.php deleted file mode 100644 index bef061b..0000000 --- a/Configuration/ConfigManager.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration; - -/** - * Central manager for index and type configuration. - */ -class ConfigManager implements ManagerInterface -{ - /** - * @var IndexConfig[] - */ - private $indexes = array(); - - /** - * @param Source\SourceInterface[] $sources - */ - public function __construct(array $sources) - { - foreach ($sources as $source) { - $this->indexes = array_merge($source->getConfiguration(), $this->indexes); - } - } - - public function getIndexConfiguration($indexName) - { - if (!$this->hasIndexConfiguration($indexName)) { - throw new \InvalidArgumentException(sprintf('Index with name "%s" is not configured.', $indexName)); - } - - return $this->indexes[$indexName]; - } - - public function getIndexNames() - { - return array_keys($this->indexes); - } - - public function getTypeConfiguration($indexName, $typeName) - { - $index = $this->getIndexConfiguration($indexName); - $type = $index->getType($typeName); - - if (!$type) { - throw new \InvalidArgumentException(sprintf('Type with name "%s" on index "%s" is not configured', $typeName, $indexName)); - } - - return $type; - } - - public function hasIndexConfiguration($indexName) - { - return isset($this->indexes[$indexName]); - } -} diff --git a/Configuration/IndexConfig.php b/Configuration/IndexConfig.php deleted file mode 100644 index 749b10d..0000000 --- a/Configuration/IndexConfig.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration; - -class IndexConfig -{ - /** - * The name of the index for ElasticSearch. - * - * @var string - */ - private $elasticSearchName; - - /** - * The internal name of the index. May not be the same as the name used in ElasticSearch, - * especially if aliases are enabled. - * - * @var string - */ - private $name; - - /** - * An array of settings sent to ElasticSearch when creating the index. - * - * @var array - */ - private $settings; - - /** - * All types that belong to this index. - * - * @var TypeConfig[] - */ - private $types; - - /** - * Indicates if the index should use an alias, allowing an index repopulation to occur - * without overwriting the current index. - * - * @var bool - */ - private $useAlias = false; - - /** - * Constructor expects an array as generated by the Container Configuration builder. - * - * @param string $name - * @param TypeConfig[] $types - * @param array $config - */ - public function __construct($name, array $types, array $config) - { - $this->elasticSearchName = isset($config['elasticSearchName']) ? $config['elasticSearchName'] : $name; - $this->name = $name; - $this->settings = isset($config['settings']) ? $config['settings'] : array(); - $this->types = $types; - $this->useAlias = isset($config['useAlias']) ? $config['useAlias'] : false; - } - - /** - * @return string - */ - public function getElasticSearchName() - { - return $this->elasticSearchName; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @return array - */ - public function getSettings() - { - return $this->settings; - } - - /** - * @param string $typeName - * - * @return TypeConfig - * - * @throws \InvalidArgumentException - */ - public function getType($typeName) - { - if (!array_key_exists($typeName, $this->types)) { - throw new \InvalidArgumentException(sprintf('Type "%s" does not exist on index "%s"', $typeName, $this->name)); - } - - return $this->types[$typeName]; - } - - /** - * @return \FOS\ElasticaBundle\Configuration\TypeConfig[] - */ - public function getTypes() - { - return $this->types; - } - - /** - * @return boolean - */ - public function isUseAlias() - { - return $this->useAlias; - } -} diff --git a/Configuration/ManagerInterface.php b/Configuration/ManagerInterface.php deleted file mode 100644 index 742df1b..0000000 --- a/Configuration/ManagerInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration; - -/** - * Central manager for index and type configuration. - */ -interface ManagerInterface -{ - /** - * Returns configuration for an index. - * - * @param $index - * - * @return IndexConfig - */ - public function getIndexConfiguration($index); - - /** - * Returns an array of known index names. - * - * @return array - */ - public function getIndexNames(); - - /** - * Returns a type configuration. - * - * @param string $index - * @param string $type - * - * @return TypeConfig - */ - public function getTypeConfiguration($index, $type); -} diff --git a/Configuration/Search.php b/Configuration/Search.php index 1d046c0..cee10ab 100644 --- a/Configuration/Search.php +++ b/Configuration/Search.php @@ -1,26 +1,16 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace FOS\ElasticaBundle\Configuration; -use FOS\ElasticaBundle\Annotation\Search as BaseSearch; - /** * Annotation class for setting search repository. * + * @author Richard Miller * @Annotation - * - * @deprecated Use FOS\ElasticaBundle\Annotation\Search instead * @Target("CLASS") */ -class Search extends BaseSearch +class Search { + /** @var string */ + public $repositoryClass; } diff --git a/Configuration/Source/ContainerSource.php b/Configuration/Source/ContainerSource.php deleted file mode 100644 index 25e6f86..0000000 --- a/Configuration/Source/ContainerSource.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration\Source; - -use FOS\ElasticaBundle\Configuration\IndexConfig; -use FOS\ElasticaBundle\Configuration\TypeConfig; - -/** - * Returns index and type configuration from the container. - */ -class ContainerSource implements SourceInterface -{ - /** - * The internal container representation of information. - * - * @var array - */ - private $configArray; - - public function __construct(array $configArray) - { - $this->configArray = $configArray; - } - - /** - * Should return all configuration available from the data source. - * - * @return IndexConfig[] - */ - public function getConfiguration() - { - $indexes = array(); - foreach ($this->configArray as $config) { - $types = $this->getTypes($config); - $index = new IndexConfig($config['name'], $types, array( - 'elasticSearchName' => $config['elasticsearch_name'], - 'settings' => $config['settings'], - 'useAlias' => $config['use_alias'], - )); - - $indexes[$config['name']] = $index; - } - - 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 deleted file mode 100644 index 05a64d0..0000000 --- a/Configuration/Source/SourceInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration\Source; - -/** - * Represents a source of index and type information (ie, the Container configuration or - * annotations). - */ -interface SourceInterface -{ - /** - * Should return all configuration available from the data source. - * - * @return \FOS\ElasticaBundle\Configuration\IndexConfig[] - */ - public function getConfiguration(); -} diff --git a/Configuration/TypeConfig.php b/Configuration/TypeConfig.php deleted file mode 100644 index a46cd34..0000000 --- a/Configuration/TypeConfig.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Configuration; - -class TypeConfig -{ - /** - * @var array - */ - private $config; - - /** - * @var array - */ - private $mapping; - - /** - * @var string - */ - private $name; - - public function __construct($name, array $mapping, array $config = array()) - { - $this->config = $config; - $this->mapping = $mapping; - $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 - */ - public function getMapping() - { - return $this->mapping; - } - - /** - * @return string|null - */ - public function getModel() - { - return isset($this->config['persistence']['model']) ? - $this->config['persistence']['model'] : - null; - } - - /** - * @return bool|null - */ - public function getNumericDetection() - { - return $this->getConfig('numeric_detection'); - } - - /** - * @return string - */ - public function getName() - { - 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 deleted file mode 100644 index 92a2489..0000000 --- a/DependencyInjection/Compiler/ConfigSourcePass.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -class ConfigSourcePass implements CompilerPassInterface -{ - /** - * {@inheritDoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('fos_elastica.config_manager')) { - return; - } - - $sources = array(); - foreach (array_keys($container->findTaggedServiceIds('fos_elastica.config_source')) as $id) { - $sources[] = new Reference($id); - } - - $container->getDefinition('fos_elastica.config_manager')->replaceArgument(0, $sources); - } -} diff --git a/DependencyInjection/Compiler/IndexPass.php b/DependencyInjection/Compiler/IndexPass.php deleted file mode 100644 index e131214..0000000 --- a/DependencyInjection/Compiler/IndexPass.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -class IndexPass implements CompilerPassInterface -{ - /** - * {@inheritDoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('fos_elastica.index_manager')) { - return; - } - - $indexes = array(); - foreach ($container->findTaggedServiceIds('fos_elastica.index') as $id => $tags) { - foreach ($tags as $tag) { - $indexes[$tag['name']] = new Reference($id); - } - } - - $container->getDefinition('fos_elastica.index_manager')->replaceArgument(0, $indexes); - } -} diff --git a/DependencyInjection/Compiler/LookupPass.php b/DependencyInjection/Compiler/LookupPass.php new file mode 100644 index 0000000..e82a56f --- /dev/null +++ b/DependencyInjection/Compiler/LookupPass.php @@ -0,0 +1,33 @@ + + */ +class LookupPass implements CompilerPassInterface +{ + /** + * {@inheritDoc} + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('fos_elastica.lookup_manager')) { + return; + } + + $lookups = array(); + foreach ($container->findTaggedServiceIds('fos_elastica.lookup') as $id => $tags) { + $lookups[] = new Reference($id); + } + + $managerDefinition = $container->getDefinition('fos_elastica.lookup_manager'); + $managerDefinition->setArguments(0, $lookups); + } +} diff --git a/DependencyInjection/Compiler/RegisterProvidersPass.php b/DependencyInjection/Compiler/RegisterProvidersPass.php index 4fd25b0..c6c9e6e 100644 --- a/DependencyInjection/Compiler/RegisterProvidersPass.php +++ b/DependencyInjection/Compiler/RegisterProvidersPass.php @@ -55,7 +55,6 @@ 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 596c732..4281d0b 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 1391eaa..b0a7b59 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -15,22 +15,17 @@ class Configuration implements ConfigurationInterface */ private $supportedDrivers = array('orm', 'mongodb', 'propel'); - /** - * If the kernel is running in debug mode. - * - * @var bool - */ - private $debug; + private $configArray = array(); - public function __construct($debug) + public function __construct($configArray) { - $this->debug = $debug; + $this->configArray = $configArray; } /** * Generates the configuration tree. * - * @return TreeBuilder + * @return \Symfony\Component\Config\Definition\NodeInterface */ public function getConfigTreeBuilder() { @@ -63,7 +58,17 @@ class Configuration implements ConfigurationInterface } /** - * Adds the configuration for the "clients" key. + * Generates the configuration tree. + * + * @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface + */ + public function getConfigTree() + { + return $this->getConfigTreeBuilder()->buildTree(); + } + + /** + * Adds the configuration for the "clients" key */ private function addClientsSection(ArrayNodeDefinition $rootNode) { @@ -74,52 +79,49 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('id') ->prototype('array') ->performNoDeepMerging() - // BC - Renaming 'servers' node to 'connections' ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['servers']); }) - ->then(function ($v) { - $v['connections'] = $v['servers']; - unset($v['servers']); - - return $v; - }) + ->ifTrue(function($v) { return isset($v['host']) && isset($v['port']); }) + ->then(function($v) { + return array( + 'servers' => array( + array( + 'host' => $v['host'], + 'port' => $v['port'], + 'logger' => isset($v['logger']) ? $v['logger'] : null, + 'headers' => isset($v['headers']) ? $v['headers'] : null, + ) + ) + ); + }) ->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), - ); - }) + ->ifTrue(function($v) { return isset($v['url']); }) + ->then(function($v) { + return array( + 'servers' => array( + array( + 'url' => $v['url'], + 'logger' => isset($v['logger']) ? $v['logger'] : null + ) + ) + ); + }) ->end() ->children() - ->arrayNode('connections') - ->requiresAtLeastOneElement() + ->arrayNode('servers') ->prototype('array') ->fixXmlConfig('header') ->children() ->scalarNode('url') ->validate() - ->ifTrue(function ($url) { return $url && substr($url, -1) !== '/'; }) - ->then(function ($url) { return $url.'/'; }) + ->ifTrue(function($url) { return substr($url, -1) !== '/'; }) + ->then(function($url) { return $url.'/'; }) ->end() ->end() ->scalarNode('host')->end() ->scalarNode('port')->end() - ->scalarNode('proxy')->end() ->scalarNode('logger') - ->defaultValue($this->debug ? 'fos_elastica.logger' : false) + ->defaultValue('%kernel.debug%') ->treatNullLike('fos_elastica.logger') ->treatTrueLike('fos_elastica.logger') ->end() @@ -127,14 +129,12 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('name') ->prototype('scalar')->end() ->end() - ->scalarNode('transport')->end() ->scalarNode('timeout')->end() ->end() ->end() ->end() ->scalarNode('timeout')->end() ->scalarNode('headers')->end() - ->scalarNode('connectionStrategy')->defaultValue('Simple')->end() ->end() ->end() ->end() @@ -143,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) { @@ -167,8 +167,61 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('index_analyzer')->end() ->scalarNode('search_analyzer')->end() - ->append($this->getPersistenceNode()) - ->append($this->getSerializerNode()) + ->arrayNode('persistence') + ->validate() + ->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']); }) + ->thenInvalid('Propel doesn\'t support the "repository" parameter') + ->end() + ->children() + ->scalarNode('driver') + ->validate() + ->ifNotInArray($this->supportedDrivers) + ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers)) + ->end() + ->end() + ->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('service')->end() + ->end() + ->end() + ->arrayNode('listener') + ->children() + ->scalarNode('insert')->defaultTrue()->end() + ->scalarNode('update')->defaultTrue()->end() + ->scalarNode('delete')->defaultTrue()->end() + ->scalarNode('persist')->defaultValue('postFlush')->end() + ->scalarNode('service')->end() + ->variableNode('is_indexable_callback')->defaultNull()->end() + ->end() + ->end() + ->arrayNode('finder') + ->children() + ->scalarNode('service')->end() + ->end() + ->end() + ->arrayNode('elastica_to_model_transformer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('hydrate')->defaultTrue()->end() + ->scalarNode('ignore_missing')->defaultFalse()->end() + ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() + ->scalarNode('service')->end() + ->end() + ->end() + ->arrayNode('model_to_elastica_transformer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('service')->end() + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->variableNode('settings')->defaultValue(array())->end() @@ -192,75 +245,84 @@ class Configuration implements ConfigurationInterface ->useAttributeAsKey('name') ->prototype('array') ->treatNullLike(array()) - ->beforeNormalization() - ->ifNull() - ->thenEmptyArray() - ->end() - // BC - Renaming 'mappings' node to 'properties' - ->beforeNormalization() - ->ifTrue(function ($v) { return array_key_exists('mappings', $v); }) - ->then(function ($v) { - $v['properties'] = $v['mappings']; - unset($v['mappings']); - - return $v; - }) - ->end() - // BC - Support the old is_indexable_callback property - ->beforeNormalization() - ->ifTrue(function ($v) { - return isset($v['persistence']) && - isset($v['persistence']['listener']) && - isset($v['persistence']['listener']['is_indexable_callback']); - }) - ->then(function ($v) { - $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; - }) - ->end() - // Support multiple dynamic_template formats to match the old bundle style - // and the way ElasticSearch expects them - ->beforeNormalization() - ->ifTrue(function ($v) { return isset($v['dynamic_templates']); }) - ->then(function ($v) { - $dt = array(); - foreach ($v['dynamic_templates'] as $key => $type) { - if (is_int($key)) { - $dt[] = $type; - } else { - $dt[][$key] = $type; - } - } - - $v['dynamic_templates'] = $dt; - - return $v; - }) - ->end() ->children() - ->booleanNode('date_detection')->end() - ->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end() + ->arrayNode('serializer') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('groups') + ->treatNullLike(array()) + ->prototype('scalar')->end() + ->end() + ->scalarNode('version')->end() + ->end() + ->end() ->scalarNode('index_analyzer')->end() - ->booleanNode('numeric_detection')->end() ->scalarNode('search_analyzer')->end() - ->variableNode('indexable_callback')->end() - ->append($this->getPersistenceNode()) - ->append($this->getSerializerNode()) + ->arrayNode('persistence') + ->validate() + ->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']); }) + ->thenInvalid('Propel doesn\'t support the "repository" parameter') + ->end() + ->children() + ->scalarNode('driver') + ->validate() + ->ifNotInArray($this->supportedDrivers) + ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers)) + ->end() + ->end() + ->scalarNode('model')->end() + ->scalarNode('repository')->end() + ->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('service')->end() + ->end() + ->end() + ->arrayNode('listener') + ->children() + ->scalarNode('insert')->defaultTrue()->end() + ->scalarNode('update')->defaultTrue()->end() + ->scalarNode('delete')->defaultTrue()->end() + ->booleanNode('immediate')->defaultFalse()->end() + ->scalarNode('logger') + ->defaultFalse() + ->treatNullLike('fos_elastica.logger') + ->treatTrueLike('fos_elastica.logger') + ->end() + ->scalarNode('service')->end() + ->variableNode('is_indexable_callback')->defaultNull()->end() + ->end() + ->end() + ->arrayNode('finder') + ->children() + ->scalarNode('service')->end() + ->end() + ->end() + ->arrayNode('elastica_to_model_transformer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('hydrate')->defaultTrue()->end() + ->scalarNode('ignore_missing')->defaultFalse()->end() + ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() + ->scalarNode('service')->end() + ->end() + ->end() + ->arrayNode('model_to_elastica_transformer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('service')->end() + ->end() + ->end() + ->end() + ->end() ->end() ->append($this->getIdNode()) - ->append($this->getPropertiesNode()) + ->append($this->getMappingsNode()) ->append($this->getDynamicTemplateNode()) ->append($this->getSourceNode()) ->append($this->getBoostNode()) @@ -276,17 +338,27 @@ class Configuration implements ConfigurationInterface } /** - * Returns the array node used for "properties". + * Returns the array node used for "mappings". */ - protected function getPropertiesNode() + protected function getMappingsNode() { $builder = new TreeBuilder(); - $node = $builder->root('properties'); + $node = $builder->root('mappings'); - $node + $nestings = $this->getNestings(); + + $childrenNode = $node ->useAttributeAsKey('name') - ->prototype('variable') - ->treatNullLike(array()); + ->prototype('array') + ->validate() + ->ifTrue(function($v) { return isset($v['fields']) && empty($v['fields']); }) + ->then(function($v) { unset($v['fields']); return $v; }) + ->end() + ->treatNullLike(array()) + ->addDefaultsIfNotSet() + ->children(); + + $this->addFieldConfig($childrenNode, $nestings); return $node; } @@ -300,21 +372,16 @@ class Configuration implements ConfigurationInterface $node = $builder->root('dynamic_templates'); $node + ->useAttributeAsKey('name') ->prototype('array') - ->prototype('array') - ->children() - ->scalarNode('match')->end() - ->scalarNode('unmatch')->end() - ->scalarNode('match_mapping_type')->end() - ->scalarNode('path_match')->end() - ->scalarNode('path_unmatch')->end() - ->scalarNode('match_pattern')->end() - ->arrayNode('mapping') - ->prototype('variable') - ->treatNullLike(array()) - ->end() - ->end() - ->end() + ->children() + ->scalarNode('match')->end() + ->scalarNode('unmatch')->end() + ->scalarNode('match_mapping_type')->end() + ->scalarNode('path_match')->end() + ->scalarNode('path_unmatch')->end() + ->scalarNode('match_pattern')->end() + ->append($this->getDynamicTemplateMapping()) ->end() ->end() ; @@ -322,6 +389,194 @@ class Configuration implements ConfigurationInterface return $node; } + /** + * @return the array node used for mapping in dynamic templates + */ + protected function getDynamicTemplateMapping() + { + $builder = new TreeBuilder(); + $node = $builder->root('mapping'); + + $nestings = $this->getNestingsForDynamicTemplates(); + + $this->addFieldConfig($node->children(), $nestings); + + return $node; + } + + /** + * @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the field config to + * @param array $nestings the nested mappings for the current field level + */ + protected function addFieldConfig($node, $nestings) + { + $node + ->scalarNode('type')->defaultValue('string')->end() + ->scalarNode('boost')->end() + ->scalarNode('store')->end() + ->scalarNode('index')->end() + ->scalarNode('index_analyzer')->end() + ->scalarNode('search_analyzer')->end() + ->scalarNode('analyzer')->end() + ->scalarNode('term_vector')->end() + ->scalarNode('null_value')->end() + ->booleanNode('include_in_all')->defaultValue(true)->end() + ->booleanNode('enabled')->defaultValue(true)->end() + ->scalarNode('lat_lon')->end() + ->scalarNode('index_name')->end() + ->booleanNode('omit_norms')->end() + ->scalarNode('index_options')->end() + ->scalarNode('ignore_above')->end() + ->scalarNode('position_offset_gap')->end() + ->arrayNode('_parent') + ->treatNullLike(array()) + ->children() + ->scalarNode('type')->end() + ->scalarNode('identifier')->defaultValue('id')->end() + ->end() + ->end() + ->scalarNode('format')->end() + ->scalarNode('similarity')->end(); + ; + + if (isset($nestings['fields'])) { + $this->addNestedFieldConfig($node, $nestings, 'fields'); + } + + if (isset($nestings['properties'])) { + $node + ->booleanNode('include_in_parent')->end() + ->booleanNode('include_in_root')->end() + ; + $this->addNestedFieldConfig($node, $nestings, 'properties'); + } + } + + /** + * @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the nested config to + * @param array $nestings The nestings for the current field level + * @param string $property the name of the nested property ('fields' or 'properties') + */ + protected function addNestedFieldConfig($node, $nestings, $property) + { + $childrenNode = $node + ->arrayNode($property) + ->useAttributeAsKey('name') + ->prototype('array') + ->validate() + ->ifTrue(function($v) { return isset($v['fields']) && empty($v['fields']); }) + ->then(function($v) { unset($v['fields']); return $v; }) + ->end() + ->treatNullLike(array()) + ->addDefaultsIfNotSet() + ->children(); + + $this->addFieldConfig($childrenNode, $nestings[$property]); + + $childrenNode + ->end() + ->end() + ->end() + ; + } + + /** + * @return array The unique nested mappings for all types + */ + protected function getNestings() + { + if (!isset($this->configArray[0]['indexes'])) { + return array(); + } + + $nestings = array(); + foreach ($this->configArray[0]['indexes'] as $index) { + if (empty($index['types'])) { + continue; + } + + foreach ($index['types'] as $type) { + if (empty($type['mappings'])) { + continue; + } + + $nestings = array_merge_recursive($nestings, $this->getNestingsForType($type['mappings'], $nestings)); + } + } + return $nestings; + } + + /** + * @return array The unique nested mappings for all dynamic templates + */ + protected function getNestingsForDynamicTemplates() + { + if (!isset($this->configArray[0]['indexes'])) { + return array(); + } + + $nestings = array(); + foreach ($this->configArray[0]['indexes'] as $index) { + if (empty($index['types'])) { + continue; + } + + foreach ($index['types'] as $type) { + if (empty($type['dynamic_templates'])) { + continue; + } + + foreach ($type['dynamic_templates'] as $definition) { + $field = $definition['mapping']; + + if (isset($field['fields'])) { + $this->addPropertyNesting($field, $nestings, 'fields'); + } else if (isset($field['properties'])) { + $this->addPropertyNesting($field, $nestings, 'properties'); + } + } + + } + } + return $nestings; + } + + /** + * @param array $mappings The mappings for the current type + * @return array The nested mappings defined for this type + */ + protected function getNestingsForType(array $mappings = null) + { + if ($mappings === null) { + return array(); + } + + $nestings = array(); + + foreach ($mappings as $field) { + if (isset($field['fields'])) { + $this->addPropertyNesting($field, $nestings, 'fields'); + } else if (isset($field['properties'])) { + $this->addPropertyNesting($field, $nestings, 'properties'); + } + } + + return $nestings; + } + + /** + * @param array $field The field mapping definition + * @param array $nestings The nestings array + * @param string $property The nested property name ('fields' or 'properties') + */ + protected function addPropertyNesting($field, &$nestings, $property) + { + if (!isset($nestings[$property])) { + $nestings[$property] = array(); + } + $nestings[$property] = array_merge_recursive($nestings[$property], $this->getNestingsForType($field[$property])); + } + /** * Returns the array node used for "_id". */ @@ -359,7 +614,7 @@ class Configuration implements ConfigurationInterface ->end() ->scalarNode('compress')->end() ->scalarNode('compress_threshold')->end() - ->scalarNode('enabled')->defaultTrue()->end() + ->scalarNode('enabled')->end() ->end() ; @@ -422,7 +677,7 @@ class Configuration implements ConfigurationInterface } /** - * Returns the array node used for "_all". + * Returns the array node used for "_all" */ protected function getAllNode() { @@ -432,8 +687,6 @@ class Configuration implements ConfigurationInterface $node ->children() ->scalarNode('enabled')->defaultValue(true)->end() - ->scalarNode('index_analyzer')->end() - ->scalarNode('search_analyzer')->end() ->end() ; @@ -441,7 +694,7 @@ class Configuration implements ConfigurationInterface } /** - * Returns the array node used for "_timestamp". + * Returns the array node used for "_timestamp" */ protected function getTimestampNode() { @@ -462,7 +715,7 @@ class Configuration implements ConfigurationInterface } /** - * Returns the array node used for "_ttl". + * Returns the array node used for "_ttl" */ protected function getTtlNode() { @@ -480,102 +733,4 @@ class Configuration implements ConfigurationInterface return $node; } - - /** - * @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition - */ - protected function getPersistenceNode() - { - $builder = new TreeBuilder(); - $node = $builder->root('persistence'); - - $node - ->validate() - ->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']); }) - ->thenInvalid('Propel doesn\'t support the "repository" parameter') - ->end() - ->children() - ->scalarNode('driver') - ->validate() - ->ifNotInArray($this->supportedDrivers) - ->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers)) - ->end() - ->end() - ->scalarNode('model')->end() - ->scalarNode('repository')->end() - ->scalarNode('identifier')->defaultValue('id')->end() - ->arrayNode('provider') - ->children() - ->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() - ->arrayNode('listener') - ->children() - ->scalarNode('insert')->defaultTrue()->end() - ->scalarNode('update')->defaultTrue()->end() - ->scalarNode('delete')->defaultTrue()->end() - ->scalarNode('flush')->defaultTrue()->end() - ->booleanNode('immediate')->defaultFalse()->end() - ->scalarNode('logger') - ->defaultFalse() - ->treatNullLike('fos_elastica.logger') - ->treatTrueLike('fos_elastica.logger') - ->end() - ->scalarNode('service')->end() - ->end() - ->end() - ->arrayNode('finder') - ->children() - ->scalarNode('service')->end() - ->end() - ->end() - ->arrayNode('elastica_to_model_transformer') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('hydrate')->defaultTrue()->end() - ->scalarNode('ignore_missing')->defaultFalse()->end() - ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() - ->scalarNode('service')->end() - ->end() - ->end() - ->arrayNode('model_to_elastica_transformer') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('service')->end() - ->end() - ->end() - ->end(); - - return $node; - } - - /** - * @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition - */ - protected function getSerializerNode() - { - $builder = new TreeBuilder(); - $node = $builder->root('serializer'); - - $node - ->addDefaultsIfNotSet() - ->children() - ->arrayNode('groups') - ->treatNullLike(array()) - ->prototype('scalar')->end() - ->end() - ->scalarNode('version')->end() - ->end(); - - return $node; - } } diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php index ec45e25..6bf1880 100644 --- a/DependencyInjection/FOSElasticaExtension.php +++ b/DependencyInjection/FOSElasticaExtension.php @@ -5,6 +5,7 @@ namespace FOS\ElasticaBundle\DependencyInjection; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Config\FileLocator; @@ -13,32 +14,20 @@ use InvalidArgumentException; class FOSElasticaExtension extends Extension { /** - * Definition of elastica clients as configured by this extension. + * Stores references to all defined clients loaded by the extension. * * @var array */ private $clients = array(); - /** - * An array of indexes as configured by the extension. - * - * @var array - */ - private $indexConfigs = array(); - - /** - * If we've encountered a type mapped to a specific persistence driver, it will be loaded - * here. - * - * @var array - */ - private $loadedDrivers = array(); + protected $indexConfigs = array(); + protected $typeFields = array(); + protected $loadedDrivers = array(); + protected $serializerConfig = array(); public function load(array $configs, ContainerBuilder $container) { - $configuration = $this->getConfiguration($configs, $container); - $config = $this->processConfiguration($configuration, $configs); - + $config = $this->processConfiguration(new Configuration($configs, $container), $configs); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); if (empty($config['clients']) || empty($config['indexes'])) { @@ -46,9 +35,7 @@ class FOSElasticaExtension extends Extension return; } - foreach (array('config', 'index', 'persister', 'provider', 'source', 'transformer') as $basename) { - $loader->load(sprintf('%s.xml', $basename)); - } + $loader->load('config.xml'); if (empty($config['default_client'])) { $keys = array_keys($config['clients']); @@ -60,45 +47,29 @@ class FOSElasticaExtension extends Extension $config['default_index'] = reset($keys); } - if (isset($config['serializer'])) { - $loader->load('serializer.xml'); - - $this->loadSerializer($config['serializer'], $container); - } - $this->loadClients($config['clients'], $container); $container->setAlias('fos_elastica.client', sprintf('fos_elastica.client.%s', $config['default_client'])); + // XX serializer can be done better.... + // $this->serializerConfig = isset($config['serializer']) ? $config['serializer'] : null; + $this->loadIndexes($config['indexes'], $container); $container->setAlias('fos_elastica.index', sprintf('fos_elastica.index.%s', $config['default_index'])); - $container->getDefinition('fos_elastica.config_source.container')->replaceArgument(0, $this->indexConfigs); - $this->loadIndexManager($container); + $this->loadResetter($container); $this->createDefaultManagerAlias($config['default_manager'], $container); } - /** - * @param array $config - * @param ContainerBuilder $container - * - * @return Configuration - */ - public function getConfiguration(array $config, ContainerBuilder $container) - { - return new Configuration($container->getParameter('kernel.debug')); - } - /** * 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) + protected function loadClients(array $clients, ContainerBuilder $container) { foreach ($clients as $name => $clientConfig) { $clientId = sprintf('fos_elastica.client.%s', $name); @@ -106,17 +77,16 @@ class FOSElasticaExtension extends Extension $clientDef = new DefinitionDecorator('fos_elastica.client_prototype'); $clientDef->replaceArgument(0, $clientConfig); - $logger = $clientConfig['connections'][0]['logger']; + $logger = $clientConfig['servers'][0]['logger']; 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) ); } } @@ -124,65 +94,66 @@ 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) + protected function loadIndexes(array $indexes, ContainerBuilder $container) { - $indexableCallbacks = array(); + $indexIds = array(); foreach ($indexes as $name => $index) { $indexId = sprintf('fos_elastica.index.%s', $name); - $indexName = isset($index['index_name']) ? $index['index_name'] : $name; + $indexName = $index['index_name'] ?: $name; $indexDef = new DefinitionDecorator('fos_elastica.index_prototype'); $indexDef->replaceArgument(0, $indexName); - $indexDef->addTag('fos_elastica.index', array( - 'name' => $name, - )); - if (isset($index['client'])) { + if ($index['client']) { $client = $this->getClient($index['client']); $indexDef->setFactoryService($client); } $container->setDefinition($indexId, $indexDef); - $reference = new Reference($indexId); + + $typePrototypeConfig = isset($index['type_prototype']) ? $index['type_prototype'] : array(); $this->indexConfigs[$name] = array( - 'elasticsearch_name' => $indexName, - 'reference' => $reference, - 'name' => $name, - 'settings' => $index['settings'], - 'type_prototype' => isset($index['type_prototype']) ? $index['type_prototype'] : array(), - 'use_alias' => $index['use_alias'], + 'index' => new Reference($indexId), + 'name_or_alias' => $indexName, + 'config' => array( + 'mappings' => array() + ) ); if ($index['finder']) { - $this->loadIndexFinder($container, $name, $reference); + // XX Deprecated + $this->loadIndexFinder($container, $name, $indexId); + } + if (!empty($index['settings'])) { + // XX What is this for? + $this->indexConfigs[$name]['config']['settings'] = $index['settings']; + } + if ($index['use_alias']) { + $this->indexConfigs[$name]['use_alias'] = true; } - $this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name], $indexableCallbacks); + $this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig); } - $indexable = $container->getDefinition('fos_elastica.indexable'); - $indexable->replaceArgument(0, $indexableCallbacks); + return $indexIds; } /** * 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 string $indexId The index service identifier * @return string */ - private function loadIndexFinder(ContainerBuilder $container, $name, Reference $index) + protected function loadIndexFinder(ContainerBuilder $container, $name, $indexId) { /* Note: transformer services may conflict with "collection.index", if * an index and type names were "collection" and an index, respectively. @@ -193,141 +164,166 @@ class FOSElasticaExtension extends Extension $finderId = sprintf('fos_elastica.finder.%s', $name); $finderDef = new DefinitionDecorator('fos_elastica.finder'); - $finderDef->replaceArgument(0, $index); + $finderDef->replaceArgument(0, new Reference($indexId)); $finderDef->replaceArgument(1, new Reference($transformerId)); $container->setDefinition($finderId, $finderDef); + + return $finderId; } /** * Loads the configured types. * - * @param array $types - * @param ContainerBuilder $container - * @param array $indexConfig - * @param array $indexableCallbacks + * @param array $types An array of types configurations + * @param ContainerBuilder $container A ContainerBuilder instance + * @param $indexName + * @param $indexId + * @param array $typePrototypeConfig + * @param $serializerConfig */ - private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig, array &$indexableCallbacks) + protected function loadTypes(array $types, ContainerBuilder $container, $indexName, $indexId, array $typePrototypeConfig) { foreach ($types as $name => $type) { - $indexName = $indexConfig['name']; - - $typeId = sprintf('%s.%s', $indexConfig['reference'], $name); - $typeDef = new DefinitionDecorator('fos_elastica.type_prototype'); - $typeDef->replaceArgument(0, $name); - $typeDef->setFactoryService($indexConfig['reference']); - $container->setDefinition($typeId, $typeDef); - - $typeConfig = array( - 'name' => $name, - 'mapping' => array(), // An array containing anything that gets sent directly to ElasticSearch - 'config' => array(), - ); - - foreach (array( - 'dynamic_templates', - 'properties', - '_all', - '_boost', - '_id', - '_parent', - '_routing', - '_source', - '_timestamp', - '_ttl', - ) as $field) { - if (isset($type[$field])) { - $typeConfig['mapping'][$field] = $type[$field]; - } - } - - foreach (array( - 'persistence', - 'serializer', - 'index_analyzer', - 'search_analyzer', - 'date_detection', - 'dynamic_date_formats', - 'numeric_detection', - ) as $field) { - $typeConfig['config'][$field] = array_key_exists($field, $type) ? - $type[$field] : - null; - } - - $this->indexConfigs[$indexName]['types'][$name] = $typeConfig; - - if (isset($type['persistence'])) { - $this->loadTypePersistenceIntegration($type['persistence'], $container, new Reference($typeId), $indexName, $name); - - $typeConfig['persistence'] = $type['persistence']; - } - - if (isset($type['indexable_callback'])) { - $indexableCallbacks[sprintf('%s/%s', $indexName, $name)] = $type['indexable_callback']; - } - - if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) { - $typeSerializerId = sprintf('%s.serializer.callback', $typeId); - $typeSerializerDef = new DefinitionDecorator('fos_elastica.serializer_callback_prototype'); + $type = self::deepArrayUnion($typePrototypeConfig, $type); + $typeId = sprintf('%s.%s', $indexId, $name); + $typeDefArgs = array($name); + $typeDef = new Definition('%fos_elastica.type.class%', $typeDefArgs); + $typeDef->setFactoryService($indexId); + $typeDef->setFactoryMethod('getType'); + if ($this->serializerConfig) { + $callbackDef = new Definition($this->serializerConfig['callback_class']); + $callbackId = sprintf('%s.%s.serializer.callback', $indexId, $name); + $typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize'))); + $callbackDef->addMethodCall('setSerializer', array(new Reference($this->serializerConfig['serializer']))); if (isset($type['serializer']['groups'])) { - $typeSerializerDef->addMethodCall('setGroups', array($type['serializer']['groups'])); + $callbackDef->addMethodCall('setGroups', array($type['serializer']['groups'])); } if (isset($type['serializer']['version'])) { - $typeSerializerDef->addMethodCall('setVersion', array($type['serializer']['version'])); + $callbackDef->addMethodCall('setVersion', array($type['serializer']['version'])); + } + $callbackClassImplementedInterfaces = class_implements($this->serializerConfig['callback_class']); // PHP < 5.4 friendly + if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) { + $callbackDef->addMethodCall('setContainer', array(new Reference('service_container'))); } - $typeDef->addMethodCall('setSerializer', array(array(new Reference($typeSerializerId), 'serialize'))); - $container->setDefinition($typeSerializerId, $typeSerializerDef); + $container->setDefinition($callbackId, $callbackDef); + + $typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize'))); + } + $container->setDefinition($typeId, $typeDef); + + $this->indexConfigs[$indexName]['config']['mappings'][$name] = array( + "_source" => array("enabled" => true), // Add a default setting for empty mapping settings + ); + + if (isset($type['_id'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_id'] = $type['_id']; + } + if (isset($type['_source'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_source'] = $type['_source']; + } + if (isset($type['_boost'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_boost'] = $type['_boost']; + } + if (isset($type['_routing'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_routing'] = $type['_routing']; + } + if (isset($type['mappings']) && !empty($type['mappings'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['properties'] = $type['mappings']; + $typeName = sprintf('%s/%s', $indexName, $name); + $this->typeFields[$typeName] = $type['mappings']; + } + if (isset($type['_parent'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_parent'] = array('type' => $type['_parent']['type']); + $typeName = sprintf('%s/%s', $indexName, $name); + $this->typeFields[$typeName]['_parent'] = $type['_parent']; + } + if (isset($type['persistence'])) { + $this->loadTypePersistenceIntegration($type['persistence'], $container, $typeDef, $indexName, $name); + } + if (isset($type['index_analyzer'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['index_analyzer'] = $type['index_analyzer']; + } + if (isset($type['search_analyzer'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['search_analyzer'] = $type['search_analyzer']; + } + if (isset($type['index'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['index'] = $type['index']; + } + if (isset($type['_all'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_all'] = $type['_all']; + } + if (isset($type['_timestamp'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_timestamp'] = $type['_timestamp']; + } + if (isset($type['_ttl'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['_ttl'] = $type['_ttl']; + } + if (!empty($type['dynamic_templates'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'] = array(); + foreach ($type['dynamic_templates'] as $templateName => $templateData) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'][] = array($templateName => $templateData); + } } } } /** - * Loads the optional provider and finder for a type. + * Merges two arrays without reindexing numeric keys. * - * @param array $typeConfig - * @param ContainerBuilder $container - * @param Reference $typeRef - * @param string $indexName - * @param string $typeName + * @param array $array1 An array to merge + * @param array $array2 An array to merge + * + * @return array The merged array */ - private function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Reference $typeRef, $indexName, $typeName) + static protected function deepArrayUnion($array1, $array2) + { + foreach ($array2 as $key => $value) { + if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) { + $array1[$key] = self::deepArrayUnion($array1[$key], $value); + } else { + $array1[$key] = $value; + } + } + + return $array1; + } + + /** + * Loads the optional provider and finder for a type + * + * @param array $typeConfig + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * @param \Symfony\Component\DependencyInjection\Definition $typeDef + * @param $indexName + * @param $typeName + */ + protected function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Definition $typeDef, $indexName, $typeName) { $this->loadDriver($container, $typeConfig['driver']); $elasticaToModelTransformerId = $this->loadElasticaToModelTransformer($typeConfig, $container, $indexName, $typeName); $modelToElasticaTransformerId = $this->loadModelToElasticaTransformer($typeConfig, $container, $indexName, $typeName); - $objectPersisterId = $this->loadObjectPersister($typeConfig, $typeRef, $container, $indexName, $typeName, $modelToElasticaTransformerId); + $objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId); if (isset($typeConfig['provider'])) { - $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $indexName, $typeName); + $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); } if (isset($typeConfig['finder'])) { - $this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeRef, $indexName, $typeName); + $this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName); } if (isset($typeConfig['listener'])) { - $this->loadTypeListener($typeConfig, $container, $objectPersisterId, $indexName, $typeName); + $this->loadTypeListener($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); } } - /** - * Creates and loads an ElasticaToModelTransformer. - * - * @param array $typeConfig - * @param ContainerBuilder $container - * @param string $indexName - * @param string $typeName - * - * @return string - */ - private function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) + protected function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) { if (isset($typeConfig['elastica_to_model_transformer']['service'])) { return $typeConfig['elastica_to_model_transformer']['service']; } - /* Note: transformer services may conflict with "prototype.driver", if * the index and type names were "prototype" and a driver, respectively. */ @@ -340,78 +336,55 @@ class FOSElasticaExtension extends Extension $argPos = ('propel' === $typeConfig['driver']) ? 0 : 1; $serviceDef->replaceArgument($argPos, $typeConfig['model']); - $serviceDef->replaceArgument($argPos + 1, array_merge($typeConfig['elastica_to_model_transformer'], array( - 'identifier' => $typeConfig['identifier'], - ))); - $container->setDefinition($serviceId, $serviceDef); - - return $serviceId; - } - - /** - * Creates and loads a ModelToElasticaTransformer for an index/type. - * - * @param array $typeConfig - * @param ContainerBuilder $container - * @param string $indexName - * @param string $typeName - * - * @return string - */ - private function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) - { - if (isset($typeConfig['model_to_elastica_transformer']['service'])) { - return $typeConfig['model_to_elastica_transformer']['service']; - } - - $abstractId = $container->hasDefinition('fos_elastica.serializer_callback_prototype') ? - 'fos_elastica.model_to_elastica_identifier_transformer' : - 'fos_elastica.model_to_elastica_transformer'; - - $serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName); - $serviceDef = new DefinitionDecorator($abstractId); - $serviceDef->replaceArgument(0, array( - 'identifier' => $typeConfig['identifier'], + $serviceDef->replaceArgument($argPos + 1, array( + 'hydrate' => $typeConfig['elastica_to_model_transformer']['hydrate'], + 'identifier' => $typeConfig['identifier'], + 'ignore_missing' => $typeConfig['elastica_to_model_transformer']['ignore_missing'], + 'query_builder_method' => $typeConfig['elastica_to_model_transformer']['query_builder_method'] )); $container->setDefinition($serviceId, $serviceDef); return $serviceId; } - /** - * Creates and loads an object persister for a type. - * - * @param array $typeConfig - * @param Reference $typeRef - * @param ContainerBuilder $container - * @param string $indexName - * @param string $typeName - * @param string $transformerId - * - * @return string - */ - private function loadObjectPersister(array $typeConfig, Reference $typeRef, ContainerBuilder $container, $indexName, $typeName, $transformerId) + protected function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) + { + if (isset($typeConfig['model_to_elastica_transformer']['service'])) { + return $typeConfig['model_to_elastica_transformer']['service']; + } + + if ($this->serializerConfig) { + $abstractId = sprintf('fos_elastica.model_to_elastica_identifier_transformer'); + } else { + $abstractId = sprintf('fos_elastica.model_to_elastica_transformer'); + } + + $serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName); + $serviceDef = new DefinitionDecorator($abstractId); + $serviceDef->replaceArgument(0, array( + 'identifier' => $typeConfig['identifier'] + )); + $container->setDefinition($serviceId, $serviceDef); + + return $serviceId; + } + + protected function loadObjectPersister(array $typeConfig, Definition $typeDef, ContainerBuilder $container, $indexName, $typeName, $transformerId) { $arguments = array( - $typeRef, + $typeDef, new Reference($transformerId), $typeConfig['model'], ); - if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) { + if ($this->serializerConfig) { $abstractId = 'fos_elastica.object_serializer_persister'; - $callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['reference'], $typeName); + $callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['index'], $typeName); $arguments[] = array(new Reference($callbackId), 'serialize'); } else { $abstractId = 'fos_elastica.object_persister'; - $mapping = $this->indexConfigs[$indexName]['types'][$typeName]['mapping']; - $argument = $mapping['properties']; - if (isset($mapping['_parent'])) { - $argument['_parent'] = $mapping['_parent']; - } - $arguments[] = $argument; + $arguments[] = $this->typeFields[sprintf('%s/%s', $indexName, $typeName)]; } - $serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName); $serviceDef = new DefinitionDecorator($abstractId); foreach ($arguments as $i => $argument) { @@ -423,58 +396,31 @@ class FOSElasticaExtension extends Extension return $serviceId; } - /** - * Loads a provider for a type. - * - * @param array $typeConfig - * @param ContainerBuilder $container - * @param string $objectPersisterId - * @param string $indexName - * @param string $typeName - * - * @return string - */ - private function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName) + protected function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName) { if (isset($typeConfig['provider']['service'])) { return $typeConfig['provider']['service']; } - /* Note: provider services may conflict with "prototype.driver", if the * 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']); + $providerDef->replaceArgument(1, $typeConfig['model']); // Propel provider can simply ignore Doctrine-specific options - $providerDef->replaceArgument(3, array_merge(array_diff_key($typeConfig['provider'], array('service' => 1)), array( - 'indexName' => $indexName, - 'typeName' => $typeName, - ))); + $providerDef->replaceArgument(2, array_diff_key($typeConfig['provider'], array('service' => 1))); $container->setDefinition($providerId, $providerDef); return $providerId; } - /** - * Loads doctrine listeners to handle indexing of new or updated objects. - * - * @param array $typeConfig - * @param ContainerBuilder $container - * @param string $objectPersisterId - * @param string $indexName - * @param string $typeName - * - * @return string - */ - private function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName) + protected function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName) { if (isset($typeConfig['listener']['service'])) { return $typeConfig['listener']['service']; } - /* Note: listener services may conflict with "prototype.driver", if the * index and type names were "prototype" and a driver, respectively. */ @@ -482,42 +428,42 @@ 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(2, array( - 'identifier' => $typeConfig['identifier'], - 'indexName' => $indexName, - 'typeName' => $typeName, - )); - $listenerDef->replaceArgument(3, $typeConfig['listener']['logger'] ? - new Reference($typeConfig['listener']['logger']) : - null - ); + $listenerDef->replaceArgument(1, $typeConfig['model']); + $listenerDef->replaceArgument(2, $this->getDoctrineEvents($typeConfig)); + $listenerDef->replaceArgument(3, $typeConfig['identifier']); + if ($typeConfig['listener']['logger']) { + $listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger'])); + } - $tagName = null; switch ($typeConfig['driver']) { - case 'orm': - $tagName = 'doctrine.event_listener'; - break; - case 'mongodb': - $tagName = 'doctrine_mongodb.odm.event_listener'; - break; + case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break; + case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break; } + if (isset($typeConfig['listener']['is_indexable_callback'])) { + $callback = $typeConfig['listener']['is_indexable_callback']; - if (null !== $tagName) { - foreach ($this->getDoctrineEvents($typeConfig) as $event) { - $listenerDef->addTag($tagName, array('event' => $event)); + if (is_array($callback)) { + list($class) = $callback + array(null); + if (is_string($class) && !class_exists($class)) { + $callback[0] = new Reference($class); + } } - } + $listenerDef->addMethodCall('setIsIndexableCallback', array($callback)); + } $container->setDefinition($listenerId, $listenerDef); return $listenerId; } /** - * Map Elastica to Doctrine events for the current driver. + * Map Elastica to Doctrine events for the current driver */ private function getDoctrineEvents(array $typeConfig) { + // Flush always calls depending on actions scheduled in lifecycle listeners + $typeConfig['listener']['flush'] = true; + switch ($typeConfig['driver']) { case 'orm': $eventsClass = '\Doctrine\ORM\Events'; @@ -527,6 +473,7 @@ class FOSElasticaExtension extends Extension break; default: throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver'])); + break; } $events = array(); @@ -534,7 +481,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) { @@ -546,26 +493,14 @@ class FOSElasticaExtension extends Extension return $events; } - /** - * Loads a Type specific Finder. - * - * @param array $typeConfig - * @param ContainerBuilder $container - * @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) + protected function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, $typeDef, $indexName, $typeName) { if (isset($typeConfig['finder']['service'])) { $finderId = $typeConfig['finder']['service']; } else { $finderId = sprintf('fos_elastica.finder.%s.%s', $indexName, $typeName); $finderDef = new DefinitionDecorator('fos_elastica.finder'); - $finderDef->replaceArgument(0, $typeRef); + $finderDef->replaceArgument(0, $typeDef); $finderDef->replaceArgument(1, new Reference($elasticaToModelId)); $container->setDefinition($finderId, $finderDef); } @@ -582,27 +517,31 @@ class FOSElasticaExtension extends Extension } /** - * Loads the index manager. + * Loads the index manager * * @param ContainerBuilder $container - **/ - private function loadIndexManager(ContainerBuilder $container) + */ + protected function loadIndexManager(ContainerBuilder $container) { - $indexRefs = array_map(function ($index) { - return $index['reference']; - }, $this->indexConfigs); + $indexRefs = array_map(function ($index) { return $index['index']; }, $this->indexConfigs); $managerDef = $container->getDefinition('fos_elastica.index_manager'); $managerDef->replaceArgument(0, $indexRefs); + $managerDef->replaceArgument(1, new Reference('fos_elastica.index')); } /** - * Makes sure a specific driver has been loaded. + * Loads the resetter * * @param ContainerBuilder $container - * @param string $driver */ - private function loadDriver(ContainerBuilder $container, $driver) + protected function loadResetter(ContainerBuilder $container) + { + $resetterDef = $container->getDefinition('fos_elastica.resetter'); + $resetterDef->replaceArgument(0, $this->indexConfigs); + } + + protected function loadDriver(ContainerBuilder $container, $driver) { if (in_array($driver, $this->loadedDrivers)) { return; @@ -613,32 +552,7 @@ class FOSElasticaExtension extends Extension $this->loadedDrivers[] = $driver; } - /** - * Loads and configures the serializer prototype. - * - * @param array $config - * @param ContainerBuilder $container - */ - private function loadSerializer($config, ContainerBuilder $container) - { - $container->setAlias('fos_elastica.serializer', $config['serializer']); - - $serializer = $container->getDefinition('fos_elastica.serializer_callback_prototype'); - $serializer->setClass($config['callback_class']); - - $callbackClassImplementedInterfaces = class_implements($config['callback_class']); - if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) { - $serializer->addMethodCall('setContainer', array(new Reference('service_container'))); - } - } - - /** - * Creates a default manager alias for defined default manager or the first loaded driver. - * - * @param string $defaultManager - * @param ContainerBuilder $container - */ - private function createDefaultManagerAlias($defaultManager, ContainerBuilder $container) + protected function createDefaultManagerAlias($defaultManager, ContainerBuilder $container) { if (0 == count($this->loadedDrivers)) { return; @@ -656,12 +570,10 @@ class FOSElasticaExtension extends Extension } /** - * Returns a reference to a client given its configured name. + * Returns a reference to a client. * * @param string $clientName - * * @return Reference - * * @throws \InvalidArgumentException */ private function getClient($clientName) diff --git a/Doctrine/AbstractElasticaToModelTransformer.php b/Doctrine/AbstractElasticaToModelTransformer.php index 0263f42..147067d 100755 --- a/Doctrine/AbstractElasticaToModelTransformer.php +++ b/Doctrine/AbstractElasticaToModelTransformer.php @@ -2,34 +2,32 @@ namespace FOS\ElasticaBundle\Doctrine; -use Doctrine\Common\Persistence\ManagerRegistry; use FOS\ElasticaBundle\HybridResult; -use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer as BaseTransformer; +use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface; 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 extends BaseTransformer +abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface { /** - * Manager registry. - * - * @var ManagerRegistry + * Manager registry */ 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 */ @@ -41,13 +39,20 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer ); /** - * Instantiates a new Mapper. + * PropertyAccessor instance * - * @param ManagerRegistry $registry - * @param string $objectClass - * @param array $options + * @var PropertyAccessorInterface */ - public function __construct(ManagerRegistry $registry, $objectClass, array $options = array()) + protected $propertyAccessor; + + /** + * Instantiates a new Mapper + * + * @param object $registry + * @param string $objectClass + * @param array $options + */ + public function __construct($registry, $objectClass, array $options = array()) { $this->registry = $registry; $this->objectClass = $objectClass; @@ -64,14 +69,22 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer 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) @@ -96,31 +109,29 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer // sort objects in the order of ids $idPos = array_flip($ids); $identifier = $this->options['identifier']; - usort($objects, $this->getSortingClosure($idPos, $identifier)); + $propertyAccessor = $this->propertyAccessor; + usort($objects, function($a, $b) use ($idPos, $identifier, $propertyAccessor) + { + return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)]; + }); return $objects; } public function hybridTransform(array $elasticaObjects) { - $indexedElasticaResults = array(); - foreach ($elasticaObjects as $elasticaObject) { - $indexedElasticaResults[$elasticaObject->getId()] = $elasticaObject; - } - $objects = $this->transform($elasticaObjects); $result = array(); - foreach ($objects as $object) { - $id = $this->propertyAccessor->getValue($object, $this->options['identifier']); - $result[] = new HybridResult($indexedElasticaResults[$id], $object); + for ($i = 0; $i < count($elasticaObjects); $i++) { + $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]); } return $result; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getIdentifierField() { @@ -128,12 +139,11 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer } /** - * Fetches objects by theses identifier values. - * - * @param array $identifierValues ids values - * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays + * Fetches objects by theses identifier values * + * @param array $identifierValues ids values + * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays * @return array of objects or arrays */ - abstract protected function findByIdentifiers(array $identifierValues, $hydrate); + protected abstract function findByIdentifiers(array $identifierValues, $hydrate); } diff --git a/Doctrine/AbstractLookup.php b/Doctrine/AbstractLookup.php new file mode 100644 index 0000000..6560bab --- /dev/null +++ b/Doctrine/AbstractLookup.php @@ -0,0 +1,22 @@ +registry = $registry; + } +} diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php index ec198a8..3fafe50 100644 --- a/Doctrine/AbstractProvider.php +++ b/Doctrine/AbstractProvider.php @@ -6,62 +6,84 @@ use Doctrine\Common\Persistence\ManagerRegistry; 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 $baseOptions + * @param array $options * @param ManagerRegistry $managerRegistry - * @param SliceFetcherInterface $sliceFetcher */ - public function __construct( - ObjectPersisterInterface $objectPersister, - IndexableInterface $indexable, - $objectClass, - array $baseOptions, - ManagerRegistry $managerRegistry, - SliceFetcherInterface $sliceFetcher = null - ) { - parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions); + public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options, $managerRegistry) + { + parent::__construct($objectPersister, $objectClass, array_merge(array( + 'clear_object_manager' => true, + 'ignore_errors' => false, + 'query_builder_method' => 'createQueryBuilder', + ), $options)); $this->managerRegistry = $managerRegistry; - $this->sliceFetcher = $sliceFetcher; + } + + /** + * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate() + */ + public function populate(\Closure $loggerClosure = null, array $options = array()) + { + $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']; + + for (; $offset < $nbObjects; $offset += $batchSize) { + if ($loggerClosure) { + $stepStartTime = microtime(true); + } + $objects = $this->fetchSlice($queryBuilder, $batchSize, $offset); + + 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']) { + $this->managerRegistry->getManagerForClass($this->objectClass)->clear(); + } + + usleep($sleep); + + if ($loggerClosure) { + $stepNbObjects = count($objects); + $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())); + } + } } /** * Counts objects that would be indexed using the query builder. * * @param object $queryBuilder - * * @return integer */ - abstract protected function countObjects($queryBuilder); - - /** - * Creates the query builder, which will be used to fetch objects to index. - * - * @param string $method - * - * @return object - */ - abstract protected function createQueryBuilder($method); + protected abstract function countObjects($queryBuilder); /** * Fetches a slice of objects using the query builder. @@ -69,102 +91,14 @@ abstract class AbstractProvider extends BaseAbstractProvider * @param object $queryBuilder * @param integer $limit * @param integer $offset - * * @return array */ - abstract protected function fetchSlice($queryBuilder, $limit, $offset); + protected abstract function fetchSlice($queryBuilder, $limit, $offset); /** - * {@inheritDoc} - */ - 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. + * Creates the query builder, which will be used to fetch objects to index. * - * @param $queryBuilder - * @param int $limit - * @param int $offset - * @param array $lastSlice - * - * @return array + * @return object */ - 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 - ); - } + protected abstract function createQueryBuilder(); } diff --git a/Doctrine/Listener.php b/Doctrine/Listener.php index a1d3585..ff9fc60 100644 --- a/Doctrine/Listener.php +++ b/Doctrine/Listener.php @@ -2,117 +2,238 @@ namespace FOS\ElasticaBundle\Doctrine; -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 Doctrine\Common\EventArgs; +use Doctrine\Common\EventSubscriber; +use FOS\ElasticaBundle\Persister\ObjectPersisterInterface; +use FOS\ElasticaBundle\Persister\ObjectPersister; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\SyntaxError; use Symfony\Component\PropertyAccess\PropertyAccess; -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 +class Listener implements EventSubscriber { /** - * Object persister. + * Object persister * - * @var ObjectPersisterInterface + * @var ObjectPersister */ protected $objectPersister; /** - * Configuration for the listener. + * Class of the domain model + * + * @var string + */ + protected $objectClass; + + /** + * List of subscribed events * * @var array */ - private $config; + protected $events; /** - * Objects scheduled for insertion. + * Name of domain model field used as the ES identifier * - * @var array + * @var string + */ + protected $esIdentifierField; + + /** + * Callback for determining if an object should be indexed + * + * @var mixed + */ + protected $isIndexableCallback; + + /** + * Objects scheduled for insertion and replacement */ public $scheduledForInsertion = array(); - - /** - * Objects scheduled to be updated or removed. - * - * @var array - */ public $scheduledForUpdate = array(); /** - * IDs of objects scheduled for removal. - * - * @var array + * IDs of objects scheduled for removal */ public $scheduledForDeletion = array(); /** - * PropertyAccessor instance. + * An instance of ExpressionLanguage + * + * @var ExpressionLanguage + */ + protected $expressionLanguage; + + /** + * PropertyAccessor instance * * @var PropertyAccessorInterface */ protected $propertyAccessor; - /** - * @var IndexableInterface - */ - private $indexable; - /** * Constructor. * * @param ObjectPersisterInterface $objectPersister - * @param IndexableInterface $indexable - * @param array $config - * @param LoggerInterface $logger + * @param string $objectClass + * @param array $events + * @param string $esIdentifierField */ - public function __construct( - ObjectPersisterInterface $objectPersister, - IndexableInterface $indexable, - array $config = array(), - LoggerInterface $logger = null - ) { - $this->config = array_merge(array( - 'identifier' => 'id', - ), $config); - $this->indexable = $indexable; - $this->objectPersister = $objectPersister; - $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); + public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $events, $esIdentifierField = 'id', $logger = null) + { + $this->objectPersister = $objectPersister; + $this->objectClass = $objectClass; + $this->events = $events; + $this->esIdentifierField = $esIdentifierField; - if ($logger && $this->objectPersister instanceof ObjectPersister) { + if ($logger) { $this->objectPersister->setLogger($logger); } + + $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } /** - * Looks for new objects that should be indexed. - * - * @param LifecycleEventArgs $eventArgs + * @see Doctrine\Common\EventSubscriber::getSubscribedEvents() */ - public function postPersist(LifecycleEventArgs $eventArgs) + public function getSubscribedEvents() { - $entity = $eventArgs->getObject(); + return $this->events; + } - if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) { + /** + * Set the callback for determining object index eligibility. + * + * If callback is a string, it must be public method on the object class + * that expects no arguments and returns a boolean. Otherwise, the callback + * should expect the object for consideration as its only argument and + * return a boolean. + * + * @param callback $callback + * @throws \RuntimeException if the callback is not callable + */ + public function setIsIndexableCallback($callback) + { + if (is_string($callback)) { + if (!is_callable(array($this->objectClass, $callback))) { + if (false !== ($expression = $this->getExpressionLanguage())) { + $callback = new Expression($callback); + try { + $expression->compile($callback, array($this->getExpressionVar())); + } catch (SyntaxError $e) { + throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable or a valid expression.', $this->objectClass, $callback), 0, $e); + } + } else { + throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $this->objectClass, $callback)); + } + } + } elseif (!is_callable($callback)) { + if (is_array($callback)) { + list($class, $method) = $callback + array(null, null); + if (is_object($class)) { + $class = get_class($class); + } + + if ($class && $method) { + throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $class, $method)); + } + } + throw new \RuntimeException('Indexable callback is not callable.'); + } + + $this->isIndexableCallback = $callback; + } + + /** + * Return whether the object is indexable with respect to the callback. + * + * @param object $object + * @return boolean + */ + protected function isObjectIndexable($object) + { + if (!$this->isIndexableCallback) { + return true; + } + + if ($this->isIndexableCallback instanceof Expression) { + return $this->getExpressionLanguage()->evaluate($this->isIndexableCallback, array($this->getExpressionVar($object) => $object)); + } + + return is_string($this->isIndexableCallback) + ? call_user_func(array($object, $this->isIndexableCallback)) + : call_user_func($this->isIndexableCallback, $object); + } + + /** + * @param mixed $object + * @return string + */ + private function getExpressionVar($object = null) + { + $class = $object ?: $this->objectClass; + $ref = new \ReflectionClass($class); + + return strtolower($ref->getShortName()); + } + + /** + * Provides unified method for retrieving a doctrine object from an EventArgs instance + * + * @param EventArgs $eventArgs + * @return object Entity | Document + * @throws \RuntimeException if no valid getter is found. + */ + private function getDoctrineObject(EventArgs $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.'); + } + + /** + * @return bool|ExpressionLanguage + */ + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + return false; + } + + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } + + public function postPersist(EventArgs $eventArgs) + { + $entity = $this->getDoctrineObject($eventArgs); + + if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) { $this->scheduledForInsertion[] = $entity; } } - /** - * Looks for objects being updated that should be indexed or removed from the index. - * - * @param LifecycleEventArgs $eventArgs - */ - public function postUpdate(LifecycleEventArgs $eventArgs) + public function postUpdate(EventArgs $eventArgs) { - $entity = $eventArgs->getObject(); + $entity = $this->getDoctrineObject($eventArgs); - if ($this->objectPersister->handlesObject($entity)) { + if ($entity instanceof $this->objectClass) { if ($this->isObjectIndexable($entity)) { $this->scheduledForUpdate[] = $entity; } else { @@ -124,22 +245,20 @@ class Listener /** * 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. - * - * @param LifecycleEventArgs $eventArgs + * preRemove, first check that the entity is managed by Doctrine */ - public function preRemove(LifecycleEventArgs $eventArgs) + public function preRemove(EventArgs $eventArgs) { - $entity = $eventArgs->getObject(); + $entity = $this->getDoctrineObject($eventArgs); - if ($this->objectPersister->handlesObject($entity)) { + if ($entity instanceof $this->objectClass) { $this->scheduleForDeletion($entity); } } /** * 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() { @@ -158,55 +277,32 @@ class Listener } /** - * 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. + * 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. */ - public function preFlush() + public function preFlush(EventArgs $eventArgs) { $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() + public function postFlush(EventArgs $eventArgs) { $this->persistScheduled(); } /** * Record the specified identifier to delete. Do not need to entire object. - * - * @param object $object + * @param mixed $object + * @return mixed */ - private function scheduleForDeletion($object) + protected function scheduleForDeletion($object) { - if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) { + if ($identifierValue = $this->propertyAccessor->getValue($object, $this->esIdentifierField)) { $this->scheduledForDeletion[] = $identifierValue; } } - - /** - * Checks if the object is indexable or not. - * - * @param object $object - * - * @return bool - */ - private function isObjectIndexable($object) - { - return $this->indexable->isObjectIndexable( - $this->config['indexName'], - $this->config['typeName'], - $object - ); - } } diff --git a/Doctrine/MongoDB/ElasticaToModelTransformer.php b/Doctrine/MongoDB/ElasticaToModelTransformer.php index 23a8292..855a093 100644 --- a/Doctrine/MongoDB/ElasticaToModelTransformer.php +++ b/Doctrine/MongoDB/ElasticaToModelTransformer.php @@ -7,23 +7,21 @@ 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. - * - * @param array $identifierValues ids values - * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays + * Fetch objects for theses identifier values * + * @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/Lookup.php b/Doctrine/MongoDB/Lookup.php new file mode 100644 index 0000000..1951964 --- /dev/null +++ b/Doctrine/MongoDB/Lookup.php @@ -0,0 +1,49 @@ +createQueryBuilder($configuration); + $qb->hydrate($configuration->isHydrate()); + + $qb->field($configuration->getIdentifierProperty()) + ->in($ids); + + return $qb->getQuery()->execute()->toArray(); + } + + /** + * @param TypeConfiguration $configuration + * @return \Doctrine\ODM\MongoDB\Query\Builder + */ + private function createQueryBuilder(TypeConfiguration $configuration) + { + $method = $configuration->getRepositoryMethod(); + $manager = $this->registry->getManagerForClass($configuration->getModelClass()); + + return $manager->{$method}($configuration->getModelClass()); + } +} diff --git a/Doctrine/MongoDB/Provider.php b/Doctrine/MongoDB/Provider.php index e4b08c5..16d9f76 100644 --- a/Doctrine/MongoDB/Provider.php +++ b/Doctrine/MongoDB/Provider.php @@ -9,42 +9,7 @@ use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException; class Provider extends AbstractProvider { /** - * Disables logging and returns the logger that was previously set. - * - * @return mixed - */ - protected function disableLogging() - { - $configuration = $this->managerRegistry - ->getManagerForClass($this->objectClass) - ->getConnection() - ->getConfiguration(); - - $logger = $configuration->getLoggerCallable(); - $configuration->setLoggerCallable(null); - - return $logger; - } - - /** - * Reenables the logger with the previously returned logger from disableLogging();. - * - * @param mixed $logger - * - * @return mixed - */ - protected function enableLogging($logger) - { - $configuration = $this->managerRegistry - ->getManagerForClass($this->objectClass) - ->getConnection() - ->getConfiguration(); - - $configuration->setLoggerCallable($logger); - } - - /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects() */ protected function countObjects($queryBuilder) { @@ -58,7 +23,7 @@ class Provider extends AbstractProvider } /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice() */ protected function fetchSlice($queryBuilder, $limit, $offset) { @@ -67,21 +32,21 @@ class Provider extends AbstractProvider } return $queryBuilder - ->skip($offset) ->limit($limit) + ->skip($offset) ->getQuery() ->execute() ->toArray(); } /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder() */ - protected function createQueryBuilder($method) + protected function createQueryBuilder() { return $this->managerRegistry ->getManagerForClass($this->objectClass) ->getRepository($this->objectClass) - ->{$method}(); + ->{$this->options['query_builder_method']}(); } } diff --git a/Doctrine/MongoDB/SliceFetcher.php b/Doctrine/MongoDB/SliceFetcher.php deleted file mode 100644 index 4723da6..0000000 --- a/Doctrine/MongoDB/SliceFetcher.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ -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 21d8640..20ec6e8 100644 --- a/Doctrine/ORM/ElasticaToModelTransformer.php +++ b/Doctrine/ORM/ElasticaToModelTransformer.php @@ -8,18 +8,17 @@ 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. - * - * @param array $identifierValues ids values - * @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays + * Fetch objects for theses identifier values * + * @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) @@ -30,14 +29,14 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer $hydrationMode = $hydrate ? Query::HYDRATE_OBJECT : Query::HYDRATE_ARRAY; $qb = $this->getEntityQueryBuilder(); - $qb->andWhere($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values')) + $qb->where($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values')) ->setParameter('values', $identifierValues); return $qb->getQuery()->setHydrationMode($hydrationMode)->execute(); } /** - * 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/Lookup.php b/Doctrine/ORM/Lookup.php new file mode 100644 index 0000000..6ec2167 --- /dev/null +++ b/Doctrine/ORM/Lookup.php @@ -0,0 +1,58 @@ +isHydrate() ? + Query::HYDRATE_OBJECT : + Query::HYDRATE_ARRAY; + + $qb = $this->createQueryBuilder($configuration); + + $qb->andWhere($qb->expr()->in( + sprintf('%s.%s', static::ENTITY_ALIAS, $configuration->getIdentifierProperty()), + ':identifiers' + )); + $qb->setParameter('identifiers', $ids); + + return $qb->getQuery()->execute(array(), $hydrationMode); + } + + /** + * @param TypeConfiguration $configuration + * @return \Doctrine\ORM\QueryBuilder + */ + private function createQueryBuilder(TypeConfiguration $configuration) + { + $repository = $this->registry->getRepository($configuration->getModelClass()); + $method = $configuration->getRepositoryMethod(); + + return $repository->{$method}(static::ENTITY_ALIAS); + } +} diff --git a/Doctrine/ORM/Provider.php b/Doctrine/ORM/Provider.php index 85b5279..5d0164f 100644 --- a/Doctrine/ORM/Provider.php +++ b/Doctrine/ORM/Provider.php @@ -11,42 +11,7 @@ class Provider extends AbstractProvider const ENTITY_ALIAS = 'a'; /** - * Disables logging and returns the logger that was previously set. - * - * @return mixed - */ - protected function disableLogging() - { - $configuration = $this->managerRegistry - ->getManagerForClass($this->objectClass) - ->getConnection() - ->getConfiguration(); - - $logger = $configuration->getSQLLogger(); - $configuration->setSQLLogger(null); - - return $logger; - } - - /** - * Reenables the logger with the previously returned logger from disableLogging();. - * - * @param mixed $logger - * - * @return mixed - */ - protected function enableLogging($logger) - { - $configuration = $this->managerRegistry - ->getManagerForClass($this->objectClass) - ->getConnection() - ->getConfiguration(); - - $configuration->setSQLLogger($logger); - } - - /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects() */ protected function countObjects($queryBuilder) { @@ -69,9 +34,7 @@ class Provider extends AbstractProvider } /** - * This method should remain in sync with SliceFetcher::fetch until it is deprecated and removed. - * - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice() */ protected function fetchSlice($queryBuilder, $limit, $offset) { @@ -79,8 +42,8 @@ class Provider extends AbstractProvider throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); } - /* - * An orderBy DQL part is required to avoid fetching the same row twice. + /** + * 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 @@ -105,14 +68,14 @@ class Provider extends AbstractProvider } /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder() */ - protected function createQueryBuilder($method) + protected function createQueryBuilder() { return $this->managerRegistry ->getManagerForClass($this->objectClass) ->getRepository($this->objectClass) // ORM query builders require an alias argument - ->{$method}(static::ENTITY_ALIAS); + ->{$this->options['query_builder_method']}(static::ENTITY_ALIAS); } } diff --git a/Doctrine/ORM/SliceFetcher.php b/Doctrine/ORM/SliceFetcher.php deleted file mode 100644 index ac6c816..0000000 --- a/Doctrine/ORM/SliceFetcher.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ -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 0d20f64..f8867eb 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 deleted file mode 100644 index a028abf..0000000 --- a/Doctrine/SliceFetcherInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ -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/DynamicIndex.php b/DynamicIndex.php index 484a0d6..992f650 100644 --- a/DynamicIndex.php +++ b/DynamicIndex.php @@ -2,11 +2,11 @@ namespace FOS\ElasticaBundle; -use FOS\ElasticaBundle\Elastica\Index; +use FOS\ElasticaBundle\Elastica\TransformingIndex; /** * @deprecated Use \FOS\ElasticaBundle\Elastica\TransformingIndex */ -class DynamicIndex extends Index +class DynamicIndex extends TransformingIndex { } diff --git a/Elastica/Client.php b/Elastica/Client.php deleted file mode 100644 index c244eb4..0000000 --- a/Elastica/Client.php +++ /dev/null @@ -1,104 +0,0 @@ - - */ -class Client extends BaseClient -{ - /** - * Stores created indexes to avoid recreation. - * - * @var array - */ - private $indexCache = array(); - - /** - * Symfony's debugging Stopwatch. - * - * @var Stopwatch|null - */ - private $stopwatch; - - /** - * @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()) - { - if ($this->stopwatch) { - $this->stopwatch->start('es_request', 'fos_elastica'); - } - - $start = microtime(true); - $response = parent::request($path, $method, $data, $query); - - $this->logQuery($path, $method, $data, $query, $start); - - if ($this->stopwatch) { - $this->stopwatch->stop('es_request'); - } - - return $response; - } - - public function getIndex($name) - { - if (isset($this->indexCache[$name])) { - return $this->indexCache[$name]; - } - - return $this->indexCache[$name] = new Index($this, $name); - } - - /** - * Sets a stopwatch instance for debugging purposes. - * - * @param Stopwatch $stopwatch - */ - public function setStopwatch(Stopwatch $stopwatch = null) - { - $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 deleted file mode 100644 index ad3bd4d..0000000 --- a/Elastica/Index.php +++ /dev/null @@ -1,59 +0,0 @@ - - */ -class Index extends BaseIndex -{ - private $originalName; - - /** - * Stores created types to avoid recreation. - * - * @var array - */ - private $typeCache = array(); - - /** - * Returns the original name of the index if the index has been renamed for reindexing - * or realiasing purposes. - * - * @return string - */ - public function getOriginalName() - { - return $this->originalName ?: $this->_name; - } - - /** - * @param string $type - */ - public function getType($type) - { - if (isset($this->typeCache[$type])) { - return $this->typeCache[$type]; - } - - return $this->typeCache[$type] = parent::getType($type); - } - - /** - * 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 - */ - public function overrideName($name) - { - $this->originalName = $this->_name; - $this->_name = $name; - } -} diff --git a/Elastica/LoggingClient.php b/Elastica/LoggingClient.php new file mode 100644 index 0000000..faecdfc --- /dev/null +++ b/Elastica/LoggingClient.php @@ -0,0 +1,75 @@ + + */ +class LoggingClient extends Client +{ + /** + * @var CombinedResultTransformer + */ + private $resultTransformer; + + public function __construct(array $config = array(), $callback = null, CombinedResultTransformer $resultTransformer) + { + parent::__construct($config, $callback); + + $this->resultTransformer = $resultTransformer; + } + + /** + * Overridden Elastica method to return TransformingIndex instances instead of the + * default Index instances. + * + * @param string $name + * @return TransformingIndex + */ + public function getIndex($name) + { + return new TransformingIndex($this, $name, $this->resultTransformer); + } + + /** + * @return CombinedResultTransformer + */ + public function getResultTransformer() + { + return $this->resultTransformer; + } + + /** + * {@inheritdoc} + */ + public function request($path, $method = Request::GET, $data = array(), array $query = array()) + { + $start = microtime(true); + $response = parent::request($path, $method, $data, $query); + + if (null !== $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->getConfig('headers'), + ); + + $this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query); + } + + return $response; + } +} diff --git a/Elastica/TransformingIndex.php b/Elastica/TransformingIndex.php new file mode 100644 index 0000000..2bab636 --- /dev/null +++ b/Elastica/TransformingIndex.php @@ -0,0 +1,58 @@ + + * @author Tim Nagel + */ +class TransformingIndex extends Index +{ + /** + * Creates a TransformingSearch instance instead of the default Elastica Search + * + * @param string $query + * @param int|array $options + * @return TransformingSearch + */ + public function createSearch($query = '', $options = null) + { + $search = new TransformingSearch($this->getClient()); + $search->addIndex($this); + $search->setOptionsAndQuery($options, $query); + + return $search; + } + + /** + * Returns a type object for the current index with the given name + * + * @param string $type Type name + * @return TransformingType Type object + */ + public function getType($type) + { + return new TransformingType($this, $type); + } + + /** + * 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 + */ + public function overrideName($name) + { + $this->_name = $name; + } +} diff --git a/Elastica/TransformingResult.php b/Elastica/TransformingResult.php new file mode 100644 index 0000000..08d09bc --- /dev/null +++ b/Elastica/TransformingResult.php @@ -0,0 +1,51 @@ +resultSet = $resultSet; + } + + /** + * Returns the transformed result of the hit. + * + * @return mixed + */ + public function getTransformed() + { + if (null === $this->transformed) { + $this->resultSet->transform(); + } + + return $this->transformed; + } + + /** + * An internal method used to set the transformed result on the Result. + * + * @internal + */ + public function setTransformed($transformed) + { + $this->transformed = $transformed; + } +} diff --git a/Elastica/TransformingResultSet.php b/Elastica/TransformingResultSet.php new file mode 100644 index 0000000..c775cd3 --- /dev/null +++ b/Elastica/TransformingResultSet.php @@ -0,0 +1,82 @@ +resultTransformer = $resultTransformer; + } + + /** + * Overridden default method to set our TransformingResult objects. + * + * @param \Elastica\Response $response Response object + */ + protected function _init(Response $response) + { + $this->_response = $response; + $result = $response->getData(); + $this->_totalHits = isset($result['hits']['total']) ? $result['hits']['total'] : 0; + $this->_maxScore = isset($result['hits']['max_score']) ? $result['hits']['max_score'] : 0; + $this->_took = isset($result['took']) ? $result['took'] : 0; + $this->_timedOut = !empty($result['timed_out']); + if (isset($result['hits']['hits'])) { + foreach ($result['hits']['hits'] as $hit) { + $this->_results[] = new TransformingResult($hit, $this); + } + } + } + + /** + * Returns an array of transformed results. + * + * @return object[] + */ + public function getTransformed() + { + $this->transform(); + + return array_map(function (TransformingResult $result) { + return $result->getTransformed(); + }, $this->getResults()); + } + + /** + * Triggers the transformation of all Results. + */ + public function transform() + { + if ($this->transformed) { + return; + } + + if (!$this->count()) { + return; + } + + $this->resultTransformer->transform($this->getResults()); + $this->transformed = true; + } +} diff --git a/Elastica/TransformingSearch.php b/Elastica/TransformingSearch.php new file mode 100644 index 0000000..eee1a96 --- /dev/null +++ b/Elastica/TransformingSearch.php @@ -0,0 +1,73 @@ +value) + * @throws \Elastica\Exception\InvalidException + * @return TransformingResultSet + */ + public function search($query = '', $options = null) + { + $this->setOptionsAndQuery($options, $query); + + $query = $this->getQuery(); + $path = $this->getPath(); + + $params = $this->getOptions(); + + // Send scroll_id via raw HTTP body to handle cases of very large (> 4kb) ids. + if ('_search/scroll' == $path) { + $data = $params[self::OPTION_SCROLL_ID]; + unset($params[self::OPTION_SCROLL_ID]); + } else { + $data = $query->toArray(); + } + + $response = $this->getClient()->request( + $path, + Request::GET, + $data, + $params + ); + + return new TransformingResultSet($response, $query, $this->_client->getResultTransformer()); + } + + /** + * + * @param mixed $query + * @param $fullResult (default = false) By default only the total hit count is returned. If set to true, the full ResultSet including facets is returned. + * @return int|TransformingResultSet + */ + public function count($query = '', $fullResult = false) + { + $this->setOptionsAndQuery(null, $query); + + $query = $this->getQuery(); + $path = $this->getPath(); + + $response = $this->getClient()->request( + $path, + Request::GET, + $query->toArray(), + array(self::OPTION_SEARCH_TYPE => self::OPTION_SEARCH_TYPE_COUNT) + ); + $resultSet = new TransformingResultSet($response, $query, $this->_client->getResultTransformer()); + + return $fullResult ? $resultSet : $resultSet->getTotalHits(); + } +} diff --git a/Elastica/TransformingType.php b/Elastica/TransformingType.php new file mode 100644 index 0000000..f359c24 --- /dev/null +++ b/Elastica/TransformingType.php @@ -0,0 +1,25 @@ +getId() . '/_mlt'; + $query = Query::create($query); + $response = $this->request($path, Request::GET, $query->toArray(), $params); + + return new TransformingResultSet($response, $query, $this->_index->getClient()->getResultTransformer()); + } +} diff --git a/Event/IndexEvent.php b/Event/IndexEvent.php deleted file mode 100644 index ed71d78..0000000 --- a/Event/IndexEvent.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * 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 deleted file mode 100644 index b35105a..0000000 --- a/Event/IndexPopulateEvent.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * 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 deleted file mode 100644 index 871915a..0000000 --- a/Event/IndexResetEvent.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * 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 deleted file mode 100644 index 4f6871f..0000000 --- a/Event/TransformEvent.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * 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 deleted file mode 100644 index dd744f5..0000000 --- a/Event/TypePopulateEvent.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * 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 deleted file mode 100644 index 98fa2f4..0000000 --- a/Event/TypeResetEvent.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * 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 deleted file mode 100644 index 9af6ee3..0000000 --- a/Exception/AliasIsIndexException.php +++ /dev/null @@ -1,11 +0,0 @@ -addCompilerPass(new ConfigSourcePass()); - $container->addCompilerPass(new IndexPass()); $container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new TransformerPass()); } diff --git a/Finder/FinderInterface.php b/Finder/FinderInterface.php index 86dbf86..7c257de 100644 --- a/Finder/FinderInterface.php +++ b/Finder/FinderInterface.php @@ -5,13 +5,12 @@ namespace FOS\ElasticaBundle\Finder; interface FinderInterface { /** - * Searches for query results within a given limit. + * Searches for query results within a given limit * - * @param mixed $query Can be a string, an array or an \Elastica\Query object - * @param int $limit How many results to get + * @param mixed $query Can be a string, an array or an \Elastica\Query object + * @param int $limit How many results to get * @param array $options - * * @return array results */ - public function find($query, $limit = null, $options = array()); + function find($query, $limit = null, $options = array()); } diff --git a/Finder/PaginatedFinderInterface.php b/Finder/PaginatedFinderInterface.php deleted file mode 100644 index 1fc7a48..0000000 --- a/Finder/PaginatedFinderInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -search($query, $limit, $options); @@ -51,9 +50,8 @@ class TransformedFinder implements PaginatedFinderInterface * Find documents similar to one with passed id. * * @param integer $id - * @param array $params - * @param array $query - * + * @param array $params + * @param array $query * @return array of model objects **/ public function moreLikeThis($id, $params = array(), $query = array()) @@ -67,8 +65,7 @@ class TransformedFinder implements PaginatedFinderInterface /** * @param $query * @param null|int $limit - * @param array $options - * + * @param array $options * @return array */ protected function search($query, $limit = null, $options = array()) @@ -81,30 +78,4 @@ class TransformedFinder implements PaginatedFinderInterface return $results; } - - /** - * Gets a paginator wrapping the result of a search. - * - * @param string $query - * @param array $options - * - * @return Pagerfanta - */ - public function findPaginated($query, $options = array()) - { - $queryObject = Query::create($query); - $paginatorAdapter = $this->createPaginatorAdapter($queryObject, $options); - - return new Pagerfanta(new FantaPaginatorAdapter($paginatorAdapter)); - } - - /** - * {@inheritdoc} - */ - public function createPaginatorAdapter($query, $options = array()) - { - $query = Query::create($query); - - return new TransformedPaginatorAdapter($this->searchable, $query, $options, $this->transformer); - } } diff --git a/HybridResult.php b/HybridResult.php index 81499ba..ebd0e99 100644 --- a/HybridResult.php +++ b/HybridResult.php @@ -24,4 +24,4 @@ class HybridResult { return $this->result; } -} +} \ No newline at end of file diff --git a/Index/AliasProcessor.php b/Index/AliasProcessor.php deleted file mode 100644 index d8f608d..0000000 --- a/Index/AliasProcessor.php +++ /dev/null @@ -1,197 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Index; - -use Elastica\Client; -use Elastica\Exception\ExceptionInterface; -use Elastica\Request; -use FOS\ElasticaBundle\Configuration\IndexConfig; -use FOS\ElasticaBundle\Elastica\Index; -use FOS\ElasticaBundle\Exception\AliasIsIndexException; - -class AliasProcessor -{ - /** - * Sets the randomised root name for an index. - * - * @param IndexConfig $indexConfig - * @param Index $index - */ - public function setRootName(IndexConfig $indexConfig, Index $index) - { - $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 bool $force - * - * @throws AliasIsIndexException - * @throws \RuntimeException - */ - public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false) - { - $client = $index->getClient(); - - $aliasName = $indexConfig->getElasticSearchName(); - $oldIndexName = null; - $newIndexName = $index->getName(); - - try { - $oldIndexName = $this->getAliasedIndex($client, $aliasName); - } catch (AliasIsIndexException $e) { - if (!$force) { - throw $e; - } - - $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 (null !== $aliasedIndex) { - // if the alias is set - add an action to remove it - $aliasUpdateRequest['actions'][] = array( - '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), - ); - - return $aliasUpdateRequest; - } - - /** - * 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() - ); - } - - 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 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 string|null - * - * @throws AliasIsIndexException - */ - 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; - } - } - - 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 98ce870..dc270be 100644 --- a/Index/IndexManager.php +++ b/Index/IndexManager.php @@ -2,69 +2,67 @@ namespace FOS\ElasticaBundle\Index; -use FOS\ElasticaBundle\Elastica\Index; +use FOS\ElasticaBundle\Elastica\TransformingIndex; class IndexManager { /** - * @var Index + * @var TransformingIndex[] */ - private $defaultIndex; + protected $indexesByName; /** - * @var array + * @var string */ - private $indexes; + protected $defaultIndexName; /** - * @param array $indexes - * @param Index $defaultIndex + * @param TransformingIndex[] $indexesByName + * @param TransformingIndex $defaultIndex */ - public function __construct(array $indexes, Index $defaultIndex) + public function __construct(array $indexesByName, TransformingIndex $defaultIndex) { - $this->defaultIndex = $defaultIndex; - $this->indexes = $indexes; + $this->indexesByName = $indexesByName; + $this->defaultIndexName = $defaultIndex->getName(); } /** - * Gets all registered indexes. + * Gets all registered indexes * - * @return array + * @return TransformingIndex[] */ public function getAllIndexes() { - return $this->indexes; + return $this->indexesByName; } /** - * 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 - * + * @return TransformingIndex * @throws \InvalidArgumentException if no index exists for the given name */ public function getIndex($name = null) { if (null === $name) { - return $this->defaultIndex; + $name = $this->defaultIndexName; } - if (!isset($this->indexes[$name])) { + if (!isset($this->indexesByName[$name])) { throw new \InvalidArgumentException(sprintf('The index "%s" does not exist', $name)); } - return $this->indexes[$name]; + return $this->indexesByName[$name]; } /** - * Gets the default index. + * Gets the default index * - * @return Index + * @return TransformingIndex */ public function getDefaultIndex() { - return $this->defaultIndex; + return $this->getIndex($this->defaultIndexName); } } diff --git a/Index/MappingBuilder.php b/Index/MappingBuilder.php deleted file mode 100644 index e03bf54..0000000 --- a/Index/MappingBuilder.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Index; - -use FOS\ElasticaBundle\Configuration\IndexConfig; -use FOS\ElasticaBundle\Configuration\TypeConfig; - -class MappingBuilder -{ - /** - * Skip adding default information to certain fields. - * - * @var array - */ - private $skipTypes = array('completion'); - - /** - * Builds mappings for an entire index. - * - * @param IndexConfig $indexConfig - * - * @return array - */ - public function buildIndexMapping(IndexConfig $indexConfig) - { - $typeMappings = array(); - foreach ($indexConfig->getTypes() as $typeConfig) { - $typeMappings[$typeConfig->getName()] = $this->buildTypeMapping($typeConfig); - } - - $mapping = array(); - if (!empty($typeMappings)) { - $mapping['mappings'] = $typeMappings; - } - // 'warmers' => $indexConfig->getWarmers(), - - $settings = $indexConfig->getSettings(); - if (!empty($settings)) { - $mapping['settings'] = $settings; - } - - return $mapping; - } - - /** - * Builds mappings for a single type. - * - * @param TypeConfig $typeConfig - * - * @return array - */ - public function buildTypeMapping(TypeConfig $typeConfig) - { - $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; - } - - /** - * Fixes any properties and applies basic defaults for any field that does not have - * required options. - * - * @param $properties - */ - 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']); - } - if (in_array($property['type'], $this->skipTypes)) { - continue; - } - if (!isset($property['store'])) { - $property['store'] = true; - } - } - } -} diff --git a/Index/Resetter.php b/Index/Resetter.php index 2b39157..1151ed7 100644 --- a/Index/Resetter.php +++ b/Index/Resetter.php @@ -2,127 +2,77 @@ namespace FOS\ElasticaBundle\Index; +use Elastica\Exception\ExceptionInterface; use Elastica\Index; use Elastica\Exception\ResponseException; use Elastica\Type\Mapping; -use FOS\ElasticaBundle\Configuration\ConfigManager; -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 { - /** - * @var AliasProcessor - */ - private $aliasProcessor; - - /*** - * @var ConfigManager - */ - private $configManager; + protected $indexConfigsByName; /** - * @var EventDispatcherInterface + * Constructor. + * + * @param array $indexConfigsByName */ - private $dispatcher; - - /** - * @var IndexManager - */ - private $indexManager; - - /** - * @var MappingBuilder - */ - private $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; + public function __construct(array $indexConfigsByName) + { + $this->indexConfigsByName = $indexConfigsByName; } /** - * Deletes and recreates all indexes. - * - * @param bool $populating - * @param bool $force + * Deletes and recreates all indexes */ - public function resetAllIndexes($populating = false, $force = false) + public function resetAllIndexes() { - foreach ($this->configManager->getIndexNames() as $name) { - $this->resetIndex($name, $populating, $force); + foreach (array_keys($this->indexConfigsByName) as $name) { + $this->resetIndex($name); } } /** - * Deletes and recreates the named index. If populating, creates a new index - * with a randomised name for an alias to be set after population. + * Deletes and recreates the named index * * @param string $indexName - * @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, $force = false) + public function resetIndex($indexName) { - $indexConfig = $this->configManager->getIndexConfiguration($indexName); - $index = $this->indexManager->getIndex($indexName); + $indexConfig = $this->getIndexConfig($indexName); + $esIndex = $indexConfig['index']; + if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) { + $name = $indexConfig['name_or_alias']; + $name .= uniqid(); + $esIndex->overrideName($name); + $esIndex->create($indexConfig['config']); - $event = new IndexResetEvent($indexName, $populating, $force); - $this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event); - - if ($indexConfig->isUseAlias()) { - $this->aliasProcessor->setRootName($indexConfig, $index); + return; } - $mapping = $this->mappingBuilder->buildIndexMapping($indexConfig); - $index->create($mapping, true); - - if (!$populating and $indexConfig->isUseAlias()) { - $this->aliasProcessor->switchIndexAlias($indexConfig, $index, $force); - } - - $this->dispatcher->dispatch(IndexResetEvent::POST_INDEX_RESET, $event); + $esIndex->create($indexConfig['config'], true); } /** - * 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 */ public function resetIndexType($indexName, $typeName) { - $typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName); - $type = $this->indexManager->getIndex($indexName)->getType($typeName); + $indexConfig = $this->getIndexConfig($indexName); - $event = new TypeResetEvent($indexName, $typeName); - $this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event); + if (!isset($indexConfig['config']['mappings'][$typeName]['properties'])) { + throw new \InvalidArgumentException(sprintf('The mapping for index "%s" and type "%s" does not exist.', $indexName, $typeName)); + } + $type = $indexConfig['index']->getType($typeName); try { $type->delete(); } catch (ResponseException $e) { @@ -130,29 +80,167 @@ class Resetter throw $e; } } - - $mapping = new Mapping(); - foreach ($this->mappingBuilder->buildTypeMapping($typeConfig) as $name => $field) { - $mapping->setParam($name, $field); - } - + $mapping = $this->createMapping($indexConfig['config']['mappings'][$typeName]); $type->setMapping($mapping); - - $this->dispatcher->dispatch(TypeResetEvent::POST_TYPE_RESET, $event); } /** - * A command run when a population has finished. + * create type mapping object * - * @param string $indexName + * @param array $indexConfig + * @return Mapping */ + protected function createMapping($indexConfig) + { + $mapping = Mapping::create($indexConfig['properties']); + + $mappingSpecialFields = array('_uid', '_id', '_source', '_all', '_analyzer', '_boost', '_routing', '_index', '_size', '_timestamp', '_ttl', 'dynamic_templates'); + foreach ($mappingSpecialFields as $specialField) { + if (isset($indexConfig[$specialField])) { + $mapping->setParam($specialField, $indexConfig[$specialField]); + } + } + + if (isset($indexConfig['_parent'])) { + $mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type'])); + } + + return $mapping; + } + + /** + * Gets an index config by its name + * + * @param string $indexName Index name + * + * @param $indexName + * @return array + * @throws \InvalidArgumentException if no index config exists for the given name + */ + protected function getIndexConfig($indexName) + { + if (!isset($this->indexConfigsByName[$indexName])) { + throw new \InvalidArgumentException(sprintf('The configuration for index "%s" does not exist.', $indexName)); + } + + return $this->indexConfigsByName[$indexName]; + } + public function postPopulate($indexName) { - $indexConfig = $this->configManager->getIndexConfiguration($indexName); - - if ($indexConfig->isUseAlias()) { - $index = $this->indexManager->getIndex($indexName); - $this->aliasProcessor->switchIndexAlias($indexConfig, $index); + $indexConfig = $this->getIndexConfig($indexName); + if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) { + $this->switchIndexAlias($indexName); } } + + /** + * Switches the alias for given index (by key) to the newly populated index + * and deletes the old index + * + * @param string $indexName Index name + * + * @throws \RuntimeException + */ + private function switchIndexAlias($indexName) + { + $indexConfig = $this->getIndexConfig($indexName); + $esIndex = $indexConfig['index']; + $aliasName = $indexConfig['name_or_alias']; + $oldIndexName = false; + $newIndexName = $esIndex->getName(); + + $aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName); + + 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) + ) + ); + } + + // Change the alias to point to the new index + // Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic + // In 0.20.x it's atomic, but it doesn't return the old index name + $aliasUpdateRequest = array('actions' => array()); + if (count($aliasedIndexes) == 1) { + // if the alias is set - add an action to remove it + $oldIndexName = $aliasedIndexes[0]; + $aliasUpdateRequest['actions'][] = array( + 'remove' => array('index' => $oldIndexName, 'alias' => $aliasName) + ); + } + + // add an action to point the alias to the new index + $aliasUpdateRequest['actions'][] = array( + 'add' => array('index' => $newIndexName, 'alias' => $aliasName) + ); + + try { + $esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest); + } catch (ExceptionInterface $renameAliasException) { + $additionalError = ''; + // if we failed to move the alias, delete the newly built index + try { + $esIndex->delete(); + } catch (ExceptionInterface $deleteNewIndexException) { + $additionalError = sprintf( + 'Tried to delete newly built index %s, but also failed: %s', + $newIndexName, + $deleteNewIndexException->getError() + ); + } + + throw new \RuntimeException( + sprintf( + 'Failed to updated index alias: %s. %s', + $renameAliasException->getMessage(), + $additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName) + ) + ); + } + + // Delete the old index after the alias has been switched + if ($oldIndexName) { + $oldIndex = new Index($esIndex->getClient(), $oldIndexName); + try { + $oldIndex->delete(); + } catch (ExceptionInterface $deleteOldIndexException) { + throw new \RuntimeException( + sprintf( + 'Failed to delete old index %s with message: %s', + $oldIndexName, + $deleteOldIndexException->getMessage() + ) + ); + } + } + } + + /** + * Returns array of indexes which are mapped to given alias + * + * @param Index $esIndex ES Index + * @param string $aliasName Alias name + * + * @return array + */ + private function getAliasedIndexes(Index $esIndex, $aliasName) + { + $aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData(); + $aliasedIndexes = array(); + + foreach ($aliasesInfo as $indexName => $indexInfo) { + $aliases = array_keys($indexInfo['aliases']); + if (in_array($aliasName, $aliases)) { + $aliasedIndexes[] = $indexName; + } + } + + return $aliasedIndexes; + } } diff --git a/Manager/RepositoryManager.php b/Manager/RepositoryManager.php index 1a0601c..3cf8e96 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,20 +59,16 @@ class RepositoryManager implements RepositoryManagerInterface } $refClass = new \ReflectionClass($entityName); - $annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Annotation\\Search'); + $annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Configuration\\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 0a38e0e..1008371 100644 --- a/Manager/RepositoryManagerInterface.php +++ b/Manager/RepositoryManagerInterface.php @@ -12,6 +12,7 @@ use FOS\ElasticaBundle\Finder\FinderInterface; */ interface RepositoryManagerInterface { + /** * Adds entity name and its finder. * Custom repository class name can also be added. @@ -23,7 +24,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 deleted file mode 100644 index 8f9a60a..0000000 --- a/Paginator/FantaPaginatorAdapter.php +++ /dev/null @@ -1,63 +0,0 @@ -adapter = $adapter; - } - - /** - * Returns the number of results. - * - * @return integer The number of results. - */ - public function getNbResults() - { - return $this->adapter->getTotalHits(); - } - - /** - * Returns Facets. - * - * @return mixed - */ - public function getFacets() - { - return $this->adapter->getFacets(); - } - - /** - * Returns Aggregations. - * - * @return mixed - * - * @api - */ - public function getAggregations() - { - return $this->adapter->getAggregations(); - } - - /** - * Returns a slice of the results. - * - * @param integer $offset The offset. - * @param integer $length The length. - * - * @return array|\Traversable The slice. - */ - public function getSlice($offset, $length) - { - return $this->adapter->getResults($offset, $length)->toArray(); - } -} diff --git a/Paginator/PaginatorAdapterInterface.php b/Paginator/PaginatorAdapterInterface.php deleted file mode 100644 index adf7df2..0000000 --- a/Paginator/PaginatorAdapterInterface.php +++ /dev/null @@ -1,37 +0,0 @@ -searchable = $searchable; - $this->query = $query; - $this->options = $options; - } - - /** - * Returns the paginated results. - * - * @param integer $offset - * @param integer $itemCountPerPage - * - * @throws \InvalidArgumentException - * - * @return ResultSet - */ - protected function getElasticaResults($offset, $itemCountPerPage) - { - $offset = (integer) $offset; - $itemCountPerPage = (integer) $itemCountPerPage; - $size = $this->query->hasParam('size') - ? (integer) $this->query->getParam('size') - : null; - - if (null !== $size && $size < $offset + $itemCountPerPage) { - $itemCountPerPage = $size - $offset; - } - - if ($itemCountPerPage < 1) { - throw new InvalidArgumentException('$itemCountPerPage must be greater than zero'); - } - - $query = clone $this->query; - $query->setFrom($offset); - $query->setSize($itemCountPerPage); - - $resultSet = $this->searchable->search($query, $this->options); - $this->totalHits = $resultSet->getTotalHits(); - $this->facets = $resultSet->getFacets(); - $this->aggregations = $resultSet->getAggregations(); - - return $resultSet; - } - - /** - * Returns the paginated results. - * - * @param int $offset - * @param int $itemCountPerPage - * - * @return PartialResultsInterface - */ - public function getResults($offset, $itemCountPerPage) - { - return new RawPartialResults($this->getElasticaResults($offset, $itemCountPerPage)); - } - - /** - * 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($genuineTotal = false) - { - if (! isset($this->totalHits)) { - $this->totalHits = $this->searchable->count($this->query); - } - - return $this->query->hasParam('size') && !$genuineTotal - ? min($this->totalHits, (integer) $this->query->getParam('size')) - : $this->totalHits; - } - - /** - * Returns Facets. - * - * @return mixed - */ - public function getFacets() - { - if (! isset($this->facets)) { - $this->facets = $this->searchable->search($this->query)->getFacets(); - } - - return $this->facets; - } - - /** - * Returns Aggregations. - * - * @return mixed - */ - public function getAggregations() - { - if (!isset($this->aggregations)) { - $this->aggregations = $this->searchable->search($this->query)->getAggregations(); - } - - return $this->aggregations; - } - - /** - * Returns the Query. - * - * @return Query the search query - */ - public function getQuery() - { - return $this->query; - } -} diff --git a/Paginator/RawPartialResults.php b/Paginator/RawPartialResults.php deleted file mode 100644 index e45c6dd..0000000 --- a/Paginator/RawPartialResults.php +++ /dev/null @@ -1,64 +0,0 @@ -resultSet = $resultSet; - } - - /** - * {@inheritDoc} - */ - public function toArray() - { - return array_map(function (Result $result) { - return $result->getSource(); - }, $this->resultSet->getResults()); - } - - /** - * {@inheritDoc} - */ - public function getTotalHits() - { - return $this->resultSet->getTotalHits(); - } - - /** - * {@inheritDoc} - */ - public function getFacets() - { - if ($this->resultSet->hasFacets()) { - return $this->resultSet->getFacets(); - } - - return; - } - - /** - * {@inheritDoc} - */ - public function getAggregations() - { - if ($this->resultSet->hasAggregations()) { - return $this->resultSet->getAggregations(); - } - - return; - } -} diff --git a/Paginator/TransformedPaginatorAdapter.php b/Paginator/TransformedPaginatorAdapter.php deleted file mode 100644 index bf152fb..0000000 --- a/Paginator/TransformedPaginatorAdapter.php +++ /dev/null @@ -1,36 +0,0 @@ -transformer = $transformer; - } - - /** - * {@inheritDoc} - */ - public function getResults($offset, $length) - { - return new TransformedPartialResults($this->getElasticaResults($offset, $length), $this->transformer); - } -} diff --git a/Paginator/TransformedPartialResults.php b/Paginator/TransformedPartialResults.php deleted file mode 100644 index c9470c3..0000000 --- a/Paginator/TransformedPartialResults.php +++ /dev/null @@ -1,33 +0,0 @@ -transformer = $transformer; - } - - /** - * {@inheritDoc} - */ - public function toArray() - { - return $this->transformer->transform($this->resultSet->getResults()); - } -} diff --git a/Persister/ObjectPersister.php b/Persister/ObjectPersister.php index 92dc005..c279ec7 100644 --- a/Persister/ObjectPersister.php +++ b/Persister/ObjectPersister.php @@ -4,13 +4,14 @@ 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 */ @@ -30,32 +31,17 @@ class ObjectPersister implements ObjectPersisterInterface $this->fields = $fields; } - /** - * If the ObjectPersister handles a given object. - * - * @param object $object - * - * @return bool - */ - public function handlesObject($object) - { - 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) { @@ -68,47 +54,61 @@ 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 */ public function insertOne($object) { - $this->insertMany(array($object)); + $document = $this->transformToElasticaDocument($object); + $this->type->addDocument($document); } /** - * Replaces one object in the type. + * Replaces one object in the type * * @param object $object + * @return null **/ public function replaceOne($object) { - $this->replaceMany(array($object)); + $document = $this->transformToElasticaDocument($object); + try { + $this->type->deleteById($document->getId()); + } catch (NotFoundException $e) {} + $this->type->addDocument($document); } /** - * Deletes one object in the type. + * Deletes one object in the type * * @param object $object + * @return null **/ public function deleteOne($object) { - $this->deleteMany(array($object)); + $document = $this->transformToElasticaDocument($object); + try { + $this->type->deleteById($document->getId()); + } catch (NotFoundException $e) {} } /** - * 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) { - $this->deleteManyByIdentifiers(array($id)); + try { + $this->type->deleteById($id); + } catch (NotFoundException $e) {} } /** - * 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 @@ -148,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 */ @@ -166,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 */ @@ -180,10 +180,9 @@ 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 f624971..2b4c8ee 100644 --- a/Persister/ObjectPersisterInterface.php +++ b/Persister/ObjectPersisterInterface.php @@ -4,73 +4,66 @@ 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 */ - public function insertOne($object); + function insertOne($object); /** - * Replaces one object in the type. + * Replaces one object in the type * * @param object $object **/ - public function replaceOne($object); + function replaceOne($object); /** - * Deletes one object in the type. + * Deletes one object in the type * * @param object $object **/ - public function deleteOne($object); + function deleteOne($object); /** - * 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); + 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 */ - public function insertMany(array $objects); + 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 */ - public function replaceMany(array $objects); + 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 */ - public function deleteMany(array $objects); + 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 792aa9a..1a15656 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,25 +17,17 @@ 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 d143478..af5f8ab 100644 --- a/Propel/ElasticaToModelTransformer.php +++ b/Propel/ElasticaToModelTransformer.php @@ -3,7 +3,6 @@ namespace FOS\ElasticaBundle\Propel; use FOS\ElasticaBundle\HybridResult; -use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer; use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; @@ -15,7 +14,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; * * @author William Durand */ -class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer +class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface { /** * Propel model class to map to Elastica documents. @@ -34,11 +33,18 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer '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()) { @@ -46,12 +52,21 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer $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) @@ -66,7 +81,11 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer // Sort objects in the order of their IDs $idPos = array_flip($ids); $identifier = $this->options['identifier']; - $sortCallback = $this->getSortingClosure($idPos, $identifier); + $propertyAccessor = $this->propertyAccessor; + + $sortCallback = function($a, $b) use ($idPos, $identifier, $propertyAccessor) { + return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)]; + }; if (is_object($objects)) { $objects->uasort($sortCallback); @@ -85,7 +104,7 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer $objects = $this->transform($elasticaObjects); $result = array(); - for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) { + for ($i = 0; $i < count($elasticaObjects); $i++) { $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]); } @@ -116,7 +135,6 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer * * @param array $identifierValues Identifier values * @param boolean $hydrate Whether or not to hydrate the results - * * @return array */ protected function findByIdentifiers(array $identifierValues, $hydrate) @@ -127,7 +145,7 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer $query = $this->createQuery($this->objectClass, $this->options['identifier'], $identifierValues); - if (! $hydrate) { + if ( ! $hydrate) { return $query->toArray(); } @@ -140,7 +158,6 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer * @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) @@ -153,8 +170,6 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer /** * @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/Lookup.php b/Propel/Lookup.php new file mode 100644 index 0000000..1793b05 --- /dev/null +++ b/Propel/Lookup.php @@ -0,0 +1,54 @@ +createQuery($configuration, $ids); + + if (!$configuration->isHydrate()) { + return $query->toArray(); + } + + return $query->find(); + } + + /** + * Create a query to use in the findByIdentifiers() method. + * + * @param TypeConfiguration $configuration + * @param array $ids + * @return \ModelCriteria + */ + protected function createQuery(TypeConfiguration $configuration, array $ids) + { + $queryClass = $configuration->getModelClass() . 'Query'; + $query = $queryClass::create(); + $filterMethod = 'filterBy' . Inflector::camelize($configuration->getIdentifierProperty()); + + return $query->$filterMethod($ids); + } +} diff --git a/Propel/Provider.php b/Propel/Provider.php index 9864d53..393beba 100644 --- a/Propel/Provider.php +++ b/Propel/Provider.php @@ -5,69 +5,44 @@ namespace FOS\ElasticaBundle\Propel; use FOS\ElasticaBundle\Provider\AbstractProvider; /** - * Propel provider. + * Propel provider * * @author William Durand */ class Provider extends AbstractProvider { /** - * {@inheritDoc} + * @see FOS\ElasticaBundle\Provider\ProviderInterface::populate() */ - public function doPopulate($options, \Closure $loggerClosure = null) + public function populate(\Closure $loggerClosure = null, array $options = array()) { - $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']; - $offset = $options['offset']; - - for (; $offset < $nbObjects; $offset += $options['batch_size']) { - $objects = $queryClass::create() - ->limit($options['batch_size']) - ->offset($offset) - ->find() - ->getArrayCopy(); - $objects = $this->filterObjects($options, $objects); - if (!empty($objects)) { - $this->objectPersister->insertMany($objects); + for (; $offset < $nbObjects; $offset += $batchSize) { + if ($loggerClosure) { + $stepStartTime = microtime(true); } - usleep($options['sleep']); + $objects = $queryClass::create() + ->limit($batchSize) + ->offset($offset) + ->find(); + + $this->objectPersister->insertMany($objects->getArrayCopy()); + + usleep($sleep); if ($loggerClosure) { - $loggerClosure($options['batch_size'], $nbObjects); + $stepNbObjects = count($objects); + $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())); } } } - - /** - * {@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 f05ab98..2761a25 100644 --- a/Provider/AbstractProvider.php +++ b/Provider/AbstractProvider.php @@ -3,17 +3,16 @@ namespace FOS\ElasticaBundle\Provider; use FOS\ElasticaBundle\Persister\ObjectPersisterInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; /** - * AbstractProvider. + * AbstractProvider */ abstract class AbstractProvider implements ProviderInterface { /** - * @var array + * @var ObjectPersisterInterface */ - protected $baseOptions; + protected $objectPersister; /** * @var string @@ -21,154 +20,29 @@ abstract class AbstractProvider implements ProviderInterface protected $objectClass; /** - * @var ObjectPersisterInterface + * @var array */ - protected $objectPersister; - - /** - * @var OptionsResolver - */ - protected $resolver; - - /** - * @var IndexableInterface - */ - private $indexable; + protected $options; /** * Constructor. * * @param ObjectPersisterInterface $objectPersister - * @param IndexableInterface $indexable * @param string $objectClass - * @param array $baseOptions + * @param array $options */ - public function __construct( - ObjectPersisterInterface $objectPersister, - IndexableInterface $indexable, - $objectClass, - array $baseOptions = array() - ) { - $this->baseOptions = $baseOptions; - $this->indexable = $indexable; - $this->objectClass = $objectClass; + public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options = array()) + { $this->objectPersister = $objectPersister; - $this->resolver = new OptionsResolver(); - $this->configureOptions(); - } + $this->objectClass = $objectClass; - /** - * {@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( + $this->options = array_merge(array( 'batch_size' => 100, - '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; + ), $options); } /** - * 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->baseOptions['indexName'], - $this->baseOptions['typeName'], - $object - ); - } - - /** - * Get string with RAM usage information (current and peak). - * - * @deprecated To be removed in 4.0 + * Get string with RAM usage information (current and peak) * * @return string */ @@ -179,17 +53,4 @@ 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 deleted file mode 100644 index c26da5a..0000000 --- a/Provider/Indexable.php +++ /dev/null @@ -1,240 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Provider; - -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\ExpressionLanguage\Expression; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\ExpressionLanguage\SyntaxError; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyAccess\PropertyAccessorInterface; - -class Indexable implements IndexableInterface -{ - /** - * An array of raw configured callbacks for all types. - * - * @var array - */ - private $callbacks = array(); - - /** - * @var \Symfony\Component\DependencyInjection\ContainerInterface - */ - private $container; - - /** - * An instance of ExpressionLanguage. - * - * @var ExpressionLanguage - */ - private $expressionLanguage; - - /** - * An array of initialised callbacks. - * - * @var array - */ - private $initialisedCallbacks = array(); - - /** - * PropertyAccessor instance. - * - * @var PropertyAccessorInterface - */ - private $propertyAccessor; - - /** - * @param array $callbacks - * @param ContainerInterface $container - */ - public function __construct(array $callbacks, ContainerInterface $container) - { - $this->callbacks = $callbacks; - $this->container = $container; - $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); - } - - /** - * Return whether the object is indexable with respect to the callback. - * - * @param string $indexName - * @param string $typeName - * @param mixed $object - * - * @return bool - */ - public function isObjectIndexable($indexName, $typeName, $object) - { - $type = sprintf('%s/%s', $indexName, $typeName); - $callback = $this->getCallback($type, $object); - if (!$callback) { - return true; - } - - if ($callback instanceof Expression) { - return (bool) $this->getExpressionLanguage()->evaluate($callback, array( - 'object' => $object, - $this->getExpressionVar($object) => $object, - )); - } - - return is_string($callback) - ? call_user_func(array($object, $callback)) - : call_user_func($callback, $object); - } - - /** - * Builds and initialises a callback. - * - * @param string $type - * @param object $object - * - * @return mixed - */ - private function buildCallback($type, $object) - { - if (!array_key_exists($type, $this->callbacks)) { - return; - } - - $callback = $this->callbacks[$type]; - - if (is_callable($callback) or is_callable(array($object, $callback))) { - return $callback; - } - - if (is_array($callback) && !is_object($callback[0])) { - return $this->processArrayToCallback($type, $callback); - } - - 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) - { - if (!array_key_exists($type, $this->initialisedCallbacks)) { - $this->initialisedCallbacks[$type] = $this->buildCallback($type, $object); - } - - return $this->initialisedCallbacks[$type]; - } - - /** - * Returns the ExpressionLanguage class if it is available. - * - * @return ExpressionLanguage|null - */ - private function getExpressionLanguage() - { - if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { - $this->expressionLanguage = new ExpressionLanguage(); - } - - return $this->expressionLanguage; - } - - /** - * 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 deleted file mode 100644 index 0d9f047..0000000 --- a/Provider/IndexableInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Provider; - -interface IndexableInterface -{ - /** - * Checks if an object passed should be indexable or not. - * - * @param string $indexName - * @param string $typeName - * @param mixed $object - * - * @return bool - */ - public function isObjectIndexable($indexName, $typeName, $object); -} diff --git a/Provider/ProviderInterface.php b/Provider/ProviderInterface.php index 1ba977b..e8d7ea4 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,15 +12,9 @@ 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 */ - public function populate(\Closure $loggerClosure = null, array $options = array()); + function populate(\Closure $loggerClosure = null, array $options = array()); } diff --git a/Provider/ProviderRegistry.php b/Provider/ProviderRegistry.php index 9fc9e3c..2142223 100644 --- a/Provider/ProviderRegistry.php +++ b/Provider/ProviderRegistry.php @@ -57,10 +57,8 @@ class ProviderRegistry implements ContainerAwareInterface * * Providers will be indexed by "type" strings in the returned array. * - * @param string $index - * - * @return ProviderInterface[] - * + * @param string $index + * @return array of ProviderInterface instances * @throws \InvalidArgumentException if no providers were registered for the index */ public function getIndexProviders($index) @@ -83,9 +81,7 @@ 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 797d629..7909d2b 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,16 @@ Symfony2. Features include: > **Note** Propel support is limited and contributions fixing issues are welcome! -[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Unstable Version](https://poser.pugx.org/friendsofsymfony/elastica-bundle/v/unstable.svg)](https://packagist.org/packages/friendsofsymfony/elastica-bundle) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSElasticaBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSElasticaBundle/?branch=master) +[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) Documentation ------------- Documentation for FOSElasticaBundle is in `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 3.0.x (master)](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md) -[Read the documentation for 3.0.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/3.0.x/Resources/doc/index.md) +[Read the documentation for 2.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/2.1.x/README.md) Installation ------------ diff --git a/Repository.php b/Repository.php index 04a51c5..70b2a21 100644 --- a/Repository.php +++ b/Repository.php @@ -19,47 +19,21 @@ 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/Resetter.php b/Resetter.php index 2067579..dd24451 100644 --- a/Resetter.php +++ b/Resetter.php @@ -5,7 +5,7 @@ namespace FOS\ElasticaBundle; use FOS\ElasticaBundle\Index\Resetter as BaseResetter; /** - * @deprecated Use \FOS\ElasticaBundle\Index\Resetter + * Deletes and recreates indexes */ class Resetter extends BaseResetter { diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 06f0cda..08993dd 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -1,14 +1,13 @@ + 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\Elastica\Client + FOS\ElasticaBundle\Elastica\LoggingClient FOS\ElasticaBundle\Logger\ElasticaLogger FOS\ElasticaBundle\DataCollector\ElasticaDataCollector - FOS\ElasticaBundle\Index\MappingBuilder Symfony\Component\PropertyAccess\PropertyAccessor @@ -16,26 +15,7 @@ - - - - - - - - - - - - - - - - - - - - + @@ -44,7 +24,10 @@ - + + + + diff --git a/Resources/config/index.xml b/Resources/config/index.xml index 3ae2e50..02b2948 100644 --- a/Resources/config/index.xml +++ b/Resources/config/index.xml @@ -5,43 +5,25 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - FOS\ElasticaBundle\Index\AliasProcessor - FOS\ElasticaBundle\Finder\TransformedFinder - FOS\ElasticaBundle\Elastica\Index - FOS\ElasticaBundle\Provider\Indexable - FOS\ElasticaBundle\Index\IndexManager - FOS\ElasticaBundle\Index\Resetter + FOS\ElasticaBundle\Elastica\TransformingIndex Elastica\Type + FOS\ElasticaBundle\IndexManager + FOS\ElasticaBundle\Resetter + FOS\ElasticaBundle\Finder\TransformedFinder - - - - - - - - + - - - - - - + - - - - - + diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml index 97ed16e..0af7aa1 100644 --- a/Resources/config/mongodb.xml +++ b/Resources/config/mongodb.xml @@ -4,35 +4,23 @@ 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 + + + - + @@ -41,9 +29,11 @@ - + + + diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml index 8147d51..d44d9ae 100644 --- a/Resources/config/orm.xml +++ b/Resources/config/orm.xml @@ -1,38 +1,27 @@ - - - FOS\ElasticaBundle\Doctrine\ORM\SliceFetcher - FOS\ElasticaBundle\Doctrine\ORM\Provider - FOS\ElasticaBundle\Doctrine\Listener - FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer - FOS\ElasticaBundle\Doctrine\RepositoryManager - + 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"> - - - + - - - + - + - - - null + + + + - + @@ -41,9 +30,11 @@ - + + + diff --git a/Resources/config/propel.xml b/Resources/config/propel.xml index 297e735..7a7d93e 100644 --- a/Resources/config/propel.xml +++ b/Resources/config/propel.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + - @@ -19,8 +19,10 @@ - + + + diff --git a/Resources/config/serializer.xml b/Resources/config/serializer.xml deleted file mode 100644 index 8ee9646..0000000 --- a/Resources/config/serializer.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/Resources/config/source.xml b/Resources/config/source.xml deleted file mode 100644 index c0f085c..0000000 --- a/Resources/config/source.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/Resources/config/transformer.xml b/Resources/config/transformer.xml index 0957152..e3abbbc 100644 --- a/Resources/config/transformer.xml +++ b/Resources/config/transformer.xml @@ -12,15 +12,14 @@ - - + - + diff --git a/Resources/doc/cookbook/aliased-indexes.md b/Resources/doc/cookbook/aliased-indexes.md deleted file mode 100644 index b9049c5..0000000 --- a/Resources/doc/cookbook/aliased-indexes.md +++ /dev/null @@ -1,45 +0,0 @@ -Aliased Indexes -=============== - -You can set up FOSElasticaBundle to use aliases for indexes which allows you to run an -index population without resetting the index currently being used by the application. - -> *Note*: When you're using an alias, resetting an individual type will still cause a -> reset for that type. - -To configure FOSElasticaBundle to use aliases for an index, set the use_alias option to -true. - -```yaml -fos_elastica: - indexes: - website: - use_alias: true -``` - -The process for setting up aliases on an existing application is slightly more complicated -because the bundle is not able to set an alias as the same name as an index. You have some -options on how to handle this: - -1) Delete the index from Elasticsearch. This option will make searching unavailable in your - application until a population has completed itself, and an alias is created. - -2) Change the index_name parameter for your index to something new, and manually alias the - current index to the new index_name, which will then be replaced when you run a repopulate. - -```yaml -fos_elastica: - indexes: - website: - use_alias: true - index_name: website_prod -``` - -```bash -$ curl -XPOST 'http://localhost:9200/_aliases' -d ' -{ - "actions" : [ - { "add" : { "index" : "website", "alias" : "website_prod" } } - ] -}' -``` diff --git a/Resources/doc/cookbook/custom-properties.md b/Resources/doc/cookbook/custom-properties.md deleted file mode 100644 index 1d7687e..0000000 --- a/Resources/doc/cookbook/custom-properties.md +++ /dev/null @@ -1,33 +0,0 @@ -##### 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 866f72d..47dc3fe 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'); + /** 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 +``` _logger) { - $this->_logger->warning('Failed to send a request to ElasticSearch', array( - 'exception' => $e->getMessage(), - 'path' => $path, - 'method' => $method, - 'data' => $data, - 'query' => $query - )); - } - return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}'); } } } -``` - -Configuration change: ---------------------- - -You must update a parameter in your `app/config/config.yml` file to point to your overridden client: - -```yaml -parameters: - fos_elastica.client.class: Acme\ElasticaBundle\Client -``` +``` \ No newline at end of file diff --git a/Resources/doc/index.md b/Resources/doc/index.md index c856798..1bc093e 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -12,11 +12,7 @@ Available documentation for FOSElasticaBundle Cookbook Entries ---------------- -* [Aliased Indexes](cookbook/aliased-indexes.md) -* [Custom Indexed Properties](cookbook/custom-properties.md) * [Custom Repositories](cookbook/custom-repositories.md) * [HTTP Headers for Elastica](cookbook/elastica-client-http-headers.md) * Performance - [Logging](cookbook/logging.md) -* [Manual Providers](cookbook/manual-provider.md) -* [Clustering - Multiple Connections](cookbook/multiple-connections.md) * [Suppressing server errors](cookbook/suppress-server-errors.md) diff --git a/Resources/doc/setup.md b/Resources/doc/setup.md index 2692bb6..4ae40af 100644 --- a/Resources/doc/setup.md +++ b/Resources/doc/setup.md @@ -1,50 +1,40 @@ Step 1: Setting up the bundle ============================= -A: Download the Bundle ----------------------- +A) Install FOSElasticaBundle +---------------------------- -Open a command console, enter your project directory and execute the -following command to download the latest stable version of this bundle: +FOSElasticaBundle is installed using [Composer](https://getcomposer.org). ```bash -$ composer require friendsofsymfony/elastica-bundle +$ php composer.phar require friendsofsymfony/elastica-bundle "3.0.*" ``` -This command requires you to have Composer installed globally, as explained -in the [installation chapter](https://getcomposer.org/doc/00-intro.md) -of the Composer documentation. - ### Elasticsearch -Instructions for installing and deploying Elasticsearch may be found [here](https://www.elastic.co/downloads/elasticsearch). +Instructions for installing and deploying Elasticsearch may be found +[here](http://www.elasticsearch.org/guide/reference/setup/installation/). -Step 2: Enable the Bundle -------------------------- -Then, enable the bundle by adding the following line in the `app/AppKernel.php` -file of your project: +B) Enable FOSElasticaBundle +--------------------------- + +Enable FOSElasticaBundle in your AppKernel: ```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 ----------------------------------------------- @@ -177,52 +149,6 @@ analyzer, you could write: title: { boost: 8, analyzer: my_analyzer } ``` -Testing if an object should be indexed --------------------------------------- - -FOSElasticaBundle can be configured to automatically index changes made for -different kinds of objects if your persistence backend supports these methods, -but in some cases you might want to run an external service or call a property -on the object to see if it should be indexed. - -A property, `indexable_callback` is provided under the type configuration that -lets you configure this behaviour which will apply for any automated watching -for changes and for a repopulation of an index. - -In the example below, we're checking the enabled property on the user to only -index enabled users. - -```yaml - types: - users: - indexable_callback: 'enabled' -``` - -The callback option supports multiple approaches: - -* A method on the object itself provided as a string. `enabled` will call - `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 - information on the ExpressionLanguage component and its capabilities see its - [documentation](http://symfony.com/doc/current/components/expression_language/index.html) - -In all cases, the callback should return a true or false, with true indicating it will be -indexed, and a false indicating the object should not be indexed, or should be removed -from the index if we are running an update. - Provider Configuration ---------------------- @@ -262,20 +188,6 @@ persistence configuration. identifier: searchId ``` -### Turning on the persistence backend logger in production - -FOSElasticaBundle will turn of your persistence backend's logging configuration by default -when Symfony2 is not in debug mode. You can force FOSElasticaBundle to always disable -logging by setting debug_logging to false, to leave logging alone by setting it to true, -or leave it set to its default value which will mirror %kernel.debug%. - -```yaml - user: - persistence: - provider: - debug_logging: false -``` - Listener Configuration ---------------------- @@ -308,6 +220,49 @@ You can also choose to only listen for some of the events: > **Propel** doesn't support this feature yet. +### Checking an entity method for listener + +If you use listeners to update your index, you may need to validate your +entities before you index them (e.g. only index "public" entities). Typically, +you'll want the listener to be consistent with the provider's query criteria. +This may be achieved by using the `is_indexable_callback` config parameter: + +```yaml + persistence: + listener: + is_indexable_callback: "isPublic" +``` + +If `is_indexable_callback` is a string and the entity has a method with the +specified name, the listener will only index entities for which the method +returns `true`. Additionally, you may provide a service and method name pair: + +```yaml + persistence: + listener: + is_indexable_callback: [ "%custom_service_id%", "isIndexable" ] +``` + +In this case, the callback_class will be the `isIndexable()` method on the specified +service and the object being considered for indexing will be passed as the only +argument. This allows you to do more complex validation (e.g. ACL checks). + +If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language) +component installed, you can use expressions to evaluate the callback: + +```yaml + persistence: + listener: + is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')" +``` + +As you might expect, new entities will only be indexed if the callback_class returns +`true`. Additionally, modified entities will be updated or removed from the +index depending on whether the callback_class returns `true` or `false`, respectively. +The delete listener disregards the callback_class. + +> **Propel** doesn't support this feature yet. + Flushing Method --------------- diff --git a/Resources/doc/usage.md b/Resources/doc/usage.md index be11dbf..c1d5982 100644 --- a/Resources/doc/usage.md +++ b/Resources/doc/usage.md @@ -26,9 +26,7 @@ $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 @@ -47,7 +45,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 @@ -67,7 +65,7 @@ You can now use the index wide finder service `fos_elastica.finder.website`: ```php /** var FOS\ElasticaBundle\Finder\MappedFinder */ -$finder = $this->container->get('fos_elastica.finder.website'); +$finder = $container->get('fos_elastica.finder.website'); // Returns a mixed array of any objects mapped $results = $finder->find('bob'); @@ -93,7 +91,7 @@ An example for using a repository: ```php /** var FOS\ElasticaBundle\Manager\RepositoryManager */ -$repositoryManager = $this->container->get('fos_elastica.manager'); +$repositoryManager = $container->get('fos_elastica.manager'); /** var FOS\ElasticaBundle\Repository */ $repository = $repositoryManager->getRepository('UserBundle:User'); @@ -162,7 +160,7 @@ fos_elastica: site: settings: index: - analysis: + analysis: analyzer: my_analyzer: type: snowball @@ -186,7 +184,7 @@ The following code will execute a search against the Elasticsearch server: $finder = $this->container->get('fos_elastica.finder.site.article'); $boolQuery = new \Elastica\Query\Bool(); -$fieldQuery = new \Elastica\Query\Match(); +$fieldQuery = new \Elastica\Query\Text(); $fieldQuery->setFieldQuery('title', 'I am a title string'); $fieldQuery->setFieldParam('title', 'analyzer', 'my_analyzer'); $boolQuery->addShould($fieldQuery); diff --git a/Resources/public/images/elastica.png b/Resources/public/images/elastica.png new file mode 100644 index 0000000..dbde014 Binary files /dev/null and b/Resources/public/images/elastica.png differ diff --git a/Resources/views/Collector/elastica.html.twig b/Resources/views/Collector/elastica.html.twig index 82a3dcf..637dae7 100644 --- a/Resources/views/Collector/elastica.html.twig +++ b/Resources/views/Collector/elastica.html.twig @@ -4,9 +4,6 @@ {% set icon %} elastica {{ collector.querycount }} - {% if collector.querycount > 0 %} - in {{ '%0.2f'|format(collector.time * 1000) }} ms - {% endif %} {% endset %} {% set text %}
@@ -23,11 +20,10 @@ {% block menu %} - + Elastica {{ collector.querycount }} - {{ '%0.0f'|format(collector.time * 1000) }} ms {% endblock %} diff --git a/Serializer/Callback.php b/Serializer/Callback.php index 61da997..9fe7064 100644 --- a/Serializer/Callback.php +++ b/Serializer/Callback.php @@ -8,7 +8,7 @@ use JMS\Serializer\SerializerInterface; class Callback { protected $serializer; - protected $groups = array(); + protected $groups; protected $version; public function setSerializer($serializer) @@ -23,8 +23,10 @@ class Callback { $this->groups = $groups; - if (!empty($this->groups) && !$this->serializer instanceof SerializerInterface) { - throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".'); + if ($this->groups) { + if (!$this->serializer instanceof SerializerInterface) { + throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".'); + } } } @@ -32,16 +34,18 @@ class Callback { $this->version = $version; - if ($this->version && !$this->serializer instanceof SerializerInterface) { - throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".'); + if ($this->version) { + if (!$this->serializer instanceof SerializerInterface) { + throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".'); + } } } public function serialize($object) { - $context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array(); + $context = $this->serializer instanceof SerializerInterface ? new SerializationContext() : array(); - if (!empty($this->groups)) { + if ($this->groups) { $context->setGroups($this->groups); } diff --git a/Subscriber/PaginateElasticaQuerySubscriber.php b/Subscriber/PaginateElasticaQuerySubscriber.php index 63f6cd0..0b7cfd6 100644 --- a/Subscriber/PaginateElasticaQuerySubscriber.php +++ b/Subscriber/PaginateElasticaQuerySubscriber.php @@ -32,17 +32,13 @@ 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 */ @@ -74,7 +70,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 d63b380..b6548aa 100644 --- a/Tests/Command/ResetCommandTest.php +++ b/Tests/Command/ResetCommandTest.php @@ -2,6 +2,7 @@ namespace FOS\ElasticaBundle\Tests\Command; + use FOS\ElasticaBundle\Command\ResetCommand; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -9,8 +10,8 @@ use Symfony\Component\DependencyInjection\Container; class ResetCommandTest extends \PHPUnit_Framework_TestCase { - private $command; private $resetter; + private $indexManager; public function setup() @@ -87,4 +88,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 062db5c..5919ea7 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -6,260 +6,145 @@ use FOS\ElasticaBundle\DependencyInjection\Configuration; use Symfony\Component\Config\Definition\Processor; /** - * ConfigurationTest. + * ConfigurationTest */ class ConfigurationTest extends \PHPUnit_Framework_TestCase { /** - * @var Processor + * @var Configuration */ - private $processor; + private $configuration; public function setUp() { - $this->processor = new Processor(); + $this->configuration = new Configuration(array()); } - private function getConfigs(array $configArray) + public function testEmptyConfigContainsFormatMappingOptionNode() { - $configuration = new Configuration(true); + $tree = $this->configuration->getConfigTree(); + $children = $tree->getChildren(); + $children = $children['indexes']->getPrototype()->getChildren(); + $typeNodes = $children['types']->getPrototype()->getChildren(); + $mappings = $typeNodes['mappings']->getPrototype()->getChildren(); - return $this->processor->processConfiguration($configuration, array($configArray)); + $this->assertArrayHasKey('format', $mappings); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mappings['format']); + $this->assertNull($mappings['format']->getDefaultValue()); } - public function testUnconfiguredConfiguration() + public function testDynamicTemplateNodes() { - $configuration = $this->getConfigs(array()); + $tree = $this->configuration->getConfigTree(); + $children = $tree->getChildren(); + $children = $children['indexes']->getPrototype()->getChildren(); + $typeNodes = $children['types']->getPrototype()->getChildren(); + $dynamicTemplates = $typeNodes['dynamic_templates']->getPrototype()->getChildren(); - $this->assertSame(array( - 'clients' => array(), - 'indexes' => array(), - 'default_manager' => 'orm', - ), $configuration); + $this->assertArrayHasKey('match', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['match']); + $this->assertNull($dynamicTemplates['match']->getDefaultValue()); + + $this->assertArrayHasKey('match_mapping_type', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['match_mapping_type']); + $this->assertNull($dynamicTemplates['match_mapping_type']->getDefaultValue()); + + $this->assertArrayHasKey('unmatch', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['unmatch']); + $this->assertNull($dynamicTemplates['unmatch']->getDefaultValue()); + + $this->assertArrayHasKey('path_match', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['path_match']); + $this->assertNull($dynamicTemplates['path_match']->getDefaultValue()); + + $this->assertArrayHasKey('path_unmatch', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['path_unmatch']); + $this->assertNull($dynamicTemplates['path_unmatch']->getDefaultValue()); + + $this->assertArrayHasKey('match_pattern', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['match_pattern']); + $this->assertNull($dynamicTemplates['match_pattern']->getDefaultValue()); + + $this->assertArrayHasKey('mapping', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ArrayNode', $dynamicTemplates['mapping']); } - public function testClientConfiguration() + public function testDynamicTemplateMappingNodes() { - $configuration = $this->getConfigs(array( - 'clients' => array( - 'default' => array( - 'url' => 'http://localhost:9200', - ), - 'clustered' => array( - 'connections' => array( - array( - 'url' => 'http://es1:9200', - 'headers' => array( - 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', - ), - ), - array( - 'url' => 'http://es2:9200', - 'headers' => array( - 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', - ), - ), - ), - ), - ), - )); + $tree = $this->configuration->getConfigTree(); + $children = $tree->getChildren(); + $children = $children['indexes']->getPrototype()->getChildren(); + $typeNodes = $children['types']->getPrototype()->getChildren(); + $dynamicTemplates = $typeNodes['dynamic_templates']->getPrototype()->getChildren(); + $mapping = $dynamicTemplates['mapping']->getChildren(); - $this->assertCount(2, $configuration['clients']); - $this->assertCount(1, $configuration['clients']['default']['connections']); - $this->assertCount(0, $configuration['clients']['default']['connections'][0]['headers']); + $this->assertArrayHasKey('type', $mapping); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mapping['type']); + $this->assertSame('string', $mapping['type']->getDefaultValue()); - $this->assertCount(2, $configuration['clients']['clustered']['connections']); - $this->assertEquals('http://es2:9200/', $configuration['clients']['clustered']['connections'][1]['url']); - $this->assertCount(1, $configuration['clients']['clustered']['connections'][1]['headers']); - $this->assertEquals('Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', $configuration['clients']['clustered']['connections'][0]['headers'][0]); - } - - public function testLogging() - { - $configuration = $this->getConfigs(array( - 'clients' => array( - 'logging_enabled' => array( - 'url' => 'http://localhost:9200', - 'logger' => true, - ), - 'logging_disabled' => array( - 'url' => 'http://localhost:9200', - 'logger' => false, - ), - 'logging_not_mentioned' => array( - 'url' => 'http://localhost:9200', - ), - 'logging_custom' => array( - 'url' => 'http://localhost:9200', - 'logger' => 'custom.service', - ), - ), - )); - - $this->assertCount(4, $configuration['clients']); - - $this->assertEquals('fos_elastica.logger', $configuration['clients']['logging_enabled']['connections'][0]['logger']); - $this->assertFalse($configuration['clients']['logging_disabled']['connections'][0]['logger']); - $this->assertEquals('fos_elastica.logger', $configuration['clients']['logging_not_mentioned']['connections'][0]['logger']); - $this->assertEquals('custom.service', $configuration['clients']['logging_custom']['connections'][0]['logger']); + $this->assertArrayHasKey('index', $mapping); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mapping['index']); + $this->assertNull($mapping['index']->getDefaultValue()); } public function testSlashIsAddedAtTheEndOfServerUrl() { $config = array( 'clients' => array( - 'default' => array('url' => 'http://www.github.com'), + 'default' => array( + 'url' => 'http://www.github.com', + ), ), - ); - $configuration = $this->getConfigs($config); + ); + + $processor = new Processor(); - $this->assertEquals('http://www.github.com/', $configuration['clients']['default']['connections'][0]['url']); + $configuration = $processor->processConfiguration($this->configuration, array($config)); + + $this->assertEquals('http://www.github.com/', $configuration['clients']['default']['servers'][0]['url']); } - public function testTypeConfig() + public function testEmptyFieldsIndexIsUnset() { - $this->getConfigs(array( - 'clients' => array( - 'default' => array('url' => 'http://localhost:9200'), - ), + $config = array( 'indexes' => array( 'test' => array( - 'type_prototype' => array( - 'index_analyzer' => 'custom_analyzer', - 'persistence' => array( - 'identifier' => 'ID', - ), - 'serializer' => array( - 'groups' => array('Search'), - 'version' => 1, - ), - ), 'types' => array( 'test' => array( 'mappings' => array( - 'title' => array(), - 'published' => array('type' => 'datetime'), - 'body' => null, - ), - 'persistence' => array( - 'listener' => array( - 'logger' => true, + 'title' => array( + 'type' => 'string', + 'fields' => array( + 'autocomplete' => null + ) ), - ), - ), - 'test2' => array( - 'mappings' => array( - 'title' => null, + 'content' => null, 'children' => array( 'type' => 'nested', - ), - ), - ), - ), - ), - ), - )); - } - - public function testClientConfigurationNoUrl() - { - $configuration = $this->getConfigs(array( - 'clients' => array( - 'default' => array( - 'host' => 'localhost', - 'port' => 9200, - ), - ), - )); - - $this->assertTrue(empty($configuration['clients']['default']['connections'][0]['url'])); - } - - public function testMappingsRenamedToProperties() - { - $configuration = $this->getConfigs(array( - 'clients' => array( - 'default' => array('url' => 'http://localhost:9200'), - ), - 'indexes' => array( - 'test' => array( - 'types' => array( - 'test' => array( - 'mappings' => array( - '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( - 'clients' => array( - 'default' => array('url' => 'http://localhost:9200'), - ), - 'indexes' => array( - 'test' => array( - 'types' => array( - 'user' => array( - 'properties' => array( - 'field1' => array(), - ), - 'persistence' => array(), - ), - 'user_profile' => array( - '_parent' => array( - 'type' => 'user', - 'property' => 'owner', - ), - 'properties' => array( - 'field1' => array(), - 'field2' => array( - 'type' => 'nested', 'properties' => array( - 'nested_field1' => array( - 'type' => 'integer', + 'title' => array( + 'type' => 'string', + 'fields' => array( + 'autocomplete' => null + ) ), - 'nested_field2' => array( - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'type' => 'integer', - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - )); + 'content' => null + ) + ) + ) + ) + ) + ) + ) + ); + + $processor = new Processor(); + + $configuration = $processor->processConfiguration(new Configuration(array($config)), array($config)); + + $this->assertArrayNotHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['content']); + $this->assertArrayHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['title']); + $this->assertArrayNotHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['children']['properties']['content']); + $this->assertArrayHasKey('fields', $configuration['indexes']['test']['types']['test']['mappings']['children']['properties']['title']); } } diff --git a/Tests/DependencyInjection/FOSElasticaExtensionTest.php b/Tests/DependencyInjection/FOSElasticaExtensionTest.php deleted file mode 100644 index 1bef2b6..0000000 --- a/Tests/DependencyInjection/FOSElasticaExtensionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 5528d18..0000000 --- a/Tests/DependencyInjection/fixtures/config.yml +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 1185e74..0000000 --- a/Tests/Doctrine/AbstractElasticaToModelTransformerTest.php +++ /dev/null @@ -1,62 +0,0 @@ -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 dcaf7d6..ee657f1 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 */ @@ -11,12 +11,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase { public function testObjectInsertedOnPersist() { - $entity = new Listener\Entity(1); - $persister = $this->getMockPersister($entity, 'index', 'type'); - $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); - $indexable = $this->getMockIndexable('index', 'type', $entity, true); + $persister = $this->getMockPersister(); - $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type')); + $entity = new Listener\Entity(1); + $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); + + $listener = $this->createListener($persister, get_class($entity), array()); $listener->postPersist($eventArgs); $this->assertEquals($entity, current($listener->scheduledForInsertion)); @@ -28,14 +28,18 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase $listener->postFlush($eventArgs); } - public function testNonIndexableObjectNotInsertedOnPersist() + /** + * @dataProvider provideIsIndexableCallbacks + */ + public function testNonIndexableObjectNotInsertedOnPersist($isIndexableCallback) { - $entity = new Listener\Entity(1); - $persister = $this->getMockPersister($entity, 'index', 'type'); - $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); - $indexable = $this->getMockIndexable('index', 'type', $entity, false); + $persister = $this->getMockPersister(); - $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type')); + $entity = new Listener\Entity(1, false); + $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); + + $listener = $this->createListener($persister, get_class($entity), array()); + $listener->setIsIndexableCallback($isIndexableCallback); $listener->postPersist($eventArgs); $this->assertEmpty($listener->scheduledForInsertion); @@ -50,12 +54,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase public function testObjectReplacedOnUpdate() { - $entity = new Listener\Entity(1); - $persister = $this->getMockPersister($entity, 'index', 'type'); - $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); - $indexable = $this->getMockIndexable('index', 'type', $entity, true); + $persister = $this->getMockPersister(); - $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type')); + $entity = new Listener\Entity(1); + $eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager()); + + $listener = $this->createListener($persister, get_class($entity), array()); $listener->postUpdate($eventArgs); $this->assertEquals($entity, current($listener->scheduledForUpdate)); @@ -69,15 +73,17 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase $listener->postFlush($eventArgs); } - public function testNonIndexableObjectRemovedOnUpdate() + /** + * @dataProvider provideIsIndexableCallbacks + */ + public function testNonIndexableObjectRemovedOnUpdate($isIndexableCallback) { $classMetadata = $this->getMockClassMetadata(); $objectManager = $this->getMockObjectManager(); + $persister = $this->getMockPersister(); - $entity = new Listener\Entity(1); - $persister = $this->getMockPersister($entity, 'index', 'type'); + $entity = new Listener\Entity(1, false); $eventArgs = $this->createLifecycleEventArgs($entity, $objectManager); - $indexable = $this->getMockIndexable('index', 'type', $entity, false); $objectManager->expects($this->any()) ->method('getClassMetadata') @@ -89,7 +95,8 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase ->with($entity, 'id') ->will($this->returnValue($entity->getId())); - $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type')); + $listener = $this->createListener($persister, get_class($entity), array()); + $listener->setIsIndexableCallback($isIndexableCallback); $listener->postUpdate($eventArgs); $this->assertEmpty($listener->scheduledForUpdate); @@ -108,11 +115,10 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase { $classMetadata = $this->getMockClassMetadata(); $objectManager = $this->getMockObjectManager(); + $persister = $this->getMockPersister(); $entity = new Listener\Entity(1); - $persister = $this->getMockPersister($entity, 'index', 'type'); $eventArgs = $this->createLifecycleEventArgs($entity, $objectManager); - $indexable = $this->getMockIndexable('index', 'type', $entity); $objectManager->expects($this->any()) ->method('getClassMetadata') @@ -124,7 +130,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase ->with($entity, 'id') ->will($this->returnValue($entity->getId())); - $listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type')); + $listener = $this->createListener($persister, get_class($entity), array()); $listener->preRemove($eventArgs); $this->assertEquals($entity->getId(), current($listener->scheduledForDeletion)); @@ -140,12 +146,11 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase { $classMetadata = $this->getMockClassMetadata(); $objectManager = $this->getMockObjectManager(); + $persister = $this->getMockPersister(); $entity = new Listener\Entity(1); $entity->identifier = 'foo'; - $persister = $this->getMockPersister($entity, 'index', 'type'); $eventArgs = $this->createLifecycleEventArgs($entity, $objectManager); - $indexable = $this->getMockIndexable('index', 'type', $entity); $objectManager->expects($this->any()) ->method('getClassMetadata') @@ -157,7 +162,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase ->with($entity, 'identifier') ->will($this->returnValue($entity->getId())); - $listener = $this->createListener($persister, $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type')); + $listener = $this->createListener($persister, get_class($entity), array(), 'identifier'); $listener->preRemove($eventArgs); $this->assertEquals($entity->identifier, current($listener->scheduledForDeletion)); @@ -169,18 +174,42 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase $listener->postFlush($eventArgs); } + /** + * @dataProvider provideInvalidIsIndexableCallbacks + * @expectedException \RuntimeException + */ + public function testInvalidIsIndexableCallbacks($isIndexableCallback) + { + $listener = $this->createListener($this->getMockPersister(), 'FOS\ElasticaBundle\Tests\Doctrine\Listener\Entity', array()); + $listener->setIsIndexableCallback($isIndexableCallback); + } + + public function provideInvalidIsIndexableCallbacks() + { + return array( + array('nonexistentEntityMethod'), + array(array(new Listener\IndexableDecider(), 'internalMethod')), + array(42), + array('entity.getIsIndexable() && nonexistentEntityFunction()'), + ); + } + + public function provideIsIndexableCallbacks() + { + return array( + array('getIsIndexable'), + array(array(new Listener\IndexableDecider(), 'isIndexable')), + array(function(Listener\Entity $entity) { return $entity->getIsIndexable(); }), + array('entity.getIsIndexable()') + ); + } + abstract protected function getLifecycleEventArgsClass(); abstract protected function getListenerClass(); - /** - * @return string - */ abstract protected function getObjectManagerClass(); - /** - * @return string - */ abstract protected function getClassMetadataClass(); private function createLifecycleEventArgs() @@ -211,59 +240,9 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase ->getMock(); } - /** - * @param Listener\Entity $object - * @param string $indexName - * @param string $typeName - */ - private function getMockPersister($object, $indexName, $typeName) + private function getMockPersister() { - $mock = $this->getMockBuilder('FOS\ElasticaBundle\Persister\ObjectPersister') - ->disableOriginalConstructor() - ->getMock(); - - $mock->expects($this->any()) - ->method('handlesObject') - ->with($object) - ->will($this->returnValue(true)); - - $index = $this->getMockBuilder('Elastica\Index')->disableOriginalConstructor()->getMock(); - $index->expects($this->any()) - ->method('getName') - ->will($this->returnValue($indexName)); - $type = $this->getMockBuilder('Elastica\Type')->disableOriginalConstructor()->getMock(); - $type->expects($this->any()) - ->method('getName') - ->will($this->returnValue($typeName)); - $type->expects($this->any()) - ->method('getIndex') - ->will($this->returnValue($index)); - - $mock->expects($this->any()) - ->method('getType') - ->will($this->returnValue($type)); - - 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'); - - if (null !== $return) { - $mock->expects($this->once()) - ->method('isObjectIndexable') - ->with($indexName, $typeName, $object) - ->will($this->returnValue($return)); - } - - return $mock; + return $this->getMock('FOS\ElasticaBundle\Persister\ObjectPersisterInterface'); } } @@ -272,18 +251,33 @@ namespace FOS\ElasticaBundle\Tests\Doctrine\Listener; class Entity { private $id; - public $identifier; + private $isIndexable; - /** - * @param integer $id - */ - public function __construct($id) + public function __construct($id, $isIndexable = true) { $this->id = $id; + $this->isIndexable = $isIndexable; } public function getId() { return $this->id; } + + public function getIsIndexable() + { + return $this->isIndexable; + } +} + +class IndexableDecider +{ + public function isIndexable(Entity $entity) + { + return $entity->getIsIndexable(); + } + + protected function internalMethod() + { + } } diff --git a/Tests/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php index aa28a4c..2492eed 100644 --- a/Tests/Doctrine/AbstractProviderTest.php +++ b/Tests/Doctrine/AbstractProviderTest.php @@ -2,9 +2,6 @@ namespace FOS\ElasticaBundle\Tests\Doctrine; -use Elastica\Bulk\ResponseSet; -use Elastica\Response; - class AbstractProviderTest extends \PHPUnit_Framework_TestCase { private $objectClass; @@ -12,25 +9,24 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase private $objectPersister; private $options; private $managerRegistry; - private $indexable; - private $sliceFetcher; public function setUp() { - $this->objectClass = 'objectClass'; - $this->options = array('debug_logging' => true, 'indexName' => 'index', 'typeName' => 'type'); + if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) { + $this->markTestSkipped('Doctrine Common is not available.'); + } - $this->objectPersister = $this->getMockObjectPersister(); - $this->managerRegistry = $this->getMockManagerRegistry(); - $this->objectManager = $this->getMockObjectManager(); - $this->indexable = $this->getMockIndexable(); + $this->objectClass = 'objectClass'; + $this->options = array(); - $this->managerRegistry->expects($this->any()) - ->method('getManagerForClass') - ->with($this->objectClass) - ->will($this->returnValue($this->objectManager)); + $this->objectPersister = $this->getMockObjectPersister(); + $this->managerRegistry = $this->getMockManagerRegistry(); + $this->objectManager = $this->getMockObjectManager(); - $this->sliceFetcher = $this->getMockSliceFetcher(); + $this->managerRegistry->expects($this->any()) + ->method('getManagerForClass') + ->with($this->objectClass) + ->will($this->returnValue($this->objectManager)); } /** @@ -53,58 +49,6 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase ->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)); - - $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)); - $providerInvocationOffset = 2; foreach ($objectsByIteration as $i => $objects) { @@ -115,13 +59,14 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase ->with($queryBuilder, $batchSize, $offset) ->will($this->returnValue($objects)); + $this->objectPersister->expects($this->at($i)) + ->method('insertMany') + ->with($objects); + $this->objectManager->expects($this->at($i)) ->method('clear'); } - $this->objectPersister->expects($this->exactly(count($objectsByIteration))) - ->method('insertMany'); - $provider->populate(); } @@ -130,7 +75,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase return array( array( 100, - array(range(1, 100)), + array(range(1,100)), 100, ), array( @@ -153,47 +98,16 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase ->method('countObjects') ->will($this->returnValue($nbObjects)); - $this->sliceFetcher->expects($this->any()) - ->method('fetch') + $provider->expects($this->any()) + ->method('fetchSlice') ->will($this->returnValue($objects)); - $this->indexable->expects($this->any()) - ->method('isObjectIndexable') - ->with('index', 'type', $this->anything()) - ->will($this->returnValue(true)); - $this->objectManager->expects($this->never()) ->method('clear'); $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; @@ -205,15 +119,10 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase ->method('countObjects') ->will($this->returnValue($nbObjects)); - $this->sliceFetcher->expects($this->any()) - ->method('fetch') + $provider->expects($this->any()) + ->method('fetchSlice') ->will($this->returnValue($objects)); - $this->indexable->expects($this->any()) - ->method('isObjectIndexable') - ->with('index', 'type', $this->anything()) - ->will($this->returnValue(true)); - $loggerClosureInvoked = false; $loggerClosure = function () use (&$loggerClosureInvoked) { $loggerClosureInvoked = true; @@ -237,68 +146,29 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase ->method('countObjects') ->will($this->returnValue($nbObjects)); - $this->sliceFetcher->expects($this->any()) - ->method('fetch') + $provider->expects($this->any()) + ->method('fetchSlice') ->will($this->returnValue($objects)); - $this->indexable->expects($this->any()) - ->method('isObjectIndexable') - ->with('index', 'type', $this->anything()) - ->will($this->returnValue(true)); - $this->objectPersister->expects($this->any()) ->method('insertMany') ->will($this->throwException($this->getMockBulkResponseException())); $this->setExpectedException('Elastica\Exception\Bulk\ResponseException'); - $provider->populate(null, array('ignore_errors' => false)); - } - - public function testPopulateRunsIndexCallable() - { - $nbObjects = 2; - $objects = array(1, 2); - - $provider = $this->getMockAbstractProvider(); - $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->at(0)) - ->method('isObjectIndexable') - ->with('index', 'type', 1) - ->will($this->returnValue(false)); - $this->indexable->expects($this->at(1)) - ->method('isObjectIndexable') - ->with('index', 'type', 2) - ->will($this->returnValue(true)); - - $this->objectPersister->expects($this->once()) - ->method('insertMany') - ->with(array(2)); - - $provider->populate(); + $provider->populate(null, array('ignore-errors' => false)); } /** - * @param boolean $setSliceFetcher Whether or not to set the slice fetcher. - * * @return \FOS\ElasticaBundle\Doctrine\AbstractProvider|\PHPUnit_Framework_MockObject_MockObject */ - private function getMockAbstractProvider($setSliceFetcher = true) + private function getMockAbstractProvider() { return $this->getMockForAbstractClass('FOS\ElasticaBundle\Doctrine\AbstractProvider', array( $this->objectPersister, - $this->indexable, $this->objectClass, $this->options, $this->managerRegistry, - $setSliceFetcher ? $this->sliceFetcher : null )); } @@ -307,9 +177,9 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase */ private function getMockBulkResponseException() { - return $this->getMock('Elastica\Exception\Bulk\ResponseException', null, array( - new ResponseSet(new Response(array()), array()), - )); + return $this->getMockBuilder('Elastica\Exception\Bulk\ResponseException') + ->disableOriginalConstructor() + ->getMock(); } /** @@ -325,17 +195,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase */ private function getMockObjectManager() { - $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; + return $this->getMock(__NAMESPACE__ . '\ObjectManager'); } /** @@ -345,22 +205,6 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase { return $this->getMock('FOS\ElasticaBundle\Persister\ObjectPersisterInterface'); } - - /** - * @return \FOS\ElasticaBundle\Provider\IndexableInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private function getMockIndexable() - { - 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'); - } } /** @@ -369,7 +213,5 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase */ interface ObjectManager { - public function clear(); - public function getClassMetadata(); - public function getIdentifierFieldNames(); + function clear(); } diff --git a/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php index 607aeef..14f3ffb 100644 --- a/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php +++ b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php @@ -82,6 +82,13 @@ 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(); @@ -102,7 +109,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 36cacc6..12a89b2 100644 --- a/Tests/Doctrine/ORM/ListenerTest.php +++ b/Tests/Doctrine/ORM/ListenerTest.php @@ -6,6 +6,13 @@ 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 39f9c34..ce7b14b 100644 --- a/Tests/Doctrine/RepositoryManagerTest.php +++ b/Tests/Doctrine/RepositoryManagerTest.php @@ -4,19 +4,22 @@ 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/LoggingClientTest.php similarity index 51% rename from Tests/Elastica/ClientTest.php rename to Tests/Elastica/LoggingClientTest.php index 158b553..0b3e71d 100644 --- a/Tests/Elastica/ClientTest.php +++ b/Tests/Elastica/LoggingClientTest.php @@ -4,12 +4,36 @@ namespace FOS\ElasticaBundle\Tests\Client; use Elastica\Request; use Elastica\Transport\Null as NullTransport; +use FOS\ElasticaBundle\Elastica\LoggingClient; -class ClientTest extends \PHPUnit_Framework_TestCase +class LoggingClientTest extends \PHPUnit_Framework_TestCase { + public function testOverriddenElasticaMethods() + { + $resultTransformer = $this->getMockBuilder('FOS\ElasticaBundle\Transformer\CombinedResultTransformer') + ->disableOriginalConstructor() + ->getMock(); + $client = new LoggingClient(array(), null, $resultTransformer); + $index = $client->getIndex('index'); + $type = $index->getType('type'); + + $this->assertInstanceOf('FOS\ElasticaBundle\Elastica\TransformingIndex', $index); + $this->assertInstanceOf('FOS\ElasticaBundle\Elastica\TransformingType', $type); + } + + public function testGetResultTransformer() + { + $resultTransformer = $this->getMockBuilder('FOS\ElasticaBundle\Transformer\CombinedResultTransformer') + ->disableOriginalConstructor() + ->getMock(); + $client = new LoggingClient(array(), null, $resultTransformer); + + $this->assertSame($resultTransformer, $client->getResultTransformer()); + } + public function testRequestsAreLogged() { - $transport = new NullTransport(); + $transport = new NullTransport; $connection = $this->getMock('Elastica\Connection'); $connection->expects($this->any())->method('getTransportObject')->will($this->returnValue($transport)); @@ -28,7 +52,8 @@ class ClientTest extends \PHPUnit_Framework_TestCase $this->isType('array') ); - $client = $this->getMockBuilder('FOS\ElasticaBundle\Elastica\Client') + $client = $this->getMockBuilder('FOS\ElasticaBundle\Elastica\LoggingClient') + ->disableOriginalConstructor() ->setMethods(array('getConnection')) ->getMock(); diff --git a/Tests/Elastica/TransformingIndexTest.php b/Tests/Elastica/TransformingIndexTest.php new file mode 100644 index 0000000..e652119 --- /dev/null +++ b/Tests/Elastica/TransformingIndexTest.php @@ -0,0 +1,42 @@ +index->createSearch(); + + $this->assertInstanceOf('FOS\ElasticaBundle\Elastica\TransformingSearch', $search); + } + + public function testOverrideName() + { + $this->assertEquals('testindex', $this->index->getName()); + + $this->index->overrideName('newindex'); + + $this->assertEquals('newindex', $this->index->getName()); + } + + protected function setUp() + { + $this->client = $this->getMockBuilder('FOS\ElasticaBundle\Elastica\LoggingClient') + ->disableOriginalConstructor() + ->getMock(); + $this->index = new TransformingIndex($this->client, 'testindex'); + } +} diff --git a/Tests/Elastica/TransformingResultSetTest.php b/Tests/Elastica/TransformingResultSetTest.php new file mode 100644 index 0000000..9b9b38a --- /dev/null +++ b/Tests/Elastica/TransformingResultSetTest.php @@ -0,0 +1,42 @@ + array( + 'hits' => array( + array(), + array(), + array(), + ) + ))); + $query = new Query(); + $transformer = $this->getMockBuilder('FOS\ElasticaBundle\Transformer\CombinedResultTransformer') + ->disableOriginalConstructor() + ->getMock(); + + $resultSet = new TransformingResultSet($response, $query, $transformer); + + $this->assertCount(3, $resultSet); + $this->assertInstanceOf('FOS\ElasticaBundle\Elastica\TransformingResult', $resultSet[0]); + + $transformer->expects($this->once()) + ->method('transform') + ->with($resultSet->getResults()); + + $resultSet->transform(); + $resultSet->transform(); + + $this->assertSame(array( + 0 => null, 1 => null, 2 => null + ), $resultSet->getTransformed()); + } +} diff --git a/Tests/Elastica/TransformingResultTest.php b/Tests/Elastica/TransformingResultTest.php new file mode 100644 index 0000000..286b7d1 --- /dev/null +++ b/Tests/Elastica/TransformingResultTest.php @@ -0,0 +1,22 @@ +getMockBuilder('FOS\ElasticaBundle\Elastica\TransformingResultSet') + ->disableOriginalConstructor() + ->getMock(); + $result = new TransformingResult(array(), $resultSet); + + $resultSet->expects($this->exactly(2)) + ->method('transform'); + + $result->getTransformed(); + $result->getTransformed(); + } +} diff --git a/Tests/FOSElasticaBundleTest.php b/Tests/FOSElasticaBundleTest.php index c9513db..2bfc7f9 100644 --- a/Tests/FOSElasticaBundleTest.php +++ b/Tests/FOSElasticaBundleTest.php @@ -3,20 +3,37 @@ namespace FOS\ElasticaBundle\Tests\Resetter; use FOS\ElasticaBundle\FOSElasticaBundle; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; class FOSElasticaBundleTest extends \PHPUnit_Framework_TestCase { public function testCompilerPassesAreRegistered() { + $passes = array( + array ( + 'FOS\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass', + PassConfig::TYPE_BEFORE_REMOVING + ), + array ( + 'FOS\ElasticaBundle\DependencyInjection\Compiler\TransformerPass' + ), + ); + $container = $this ->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); $container - ->expects($this->atLeastOnce()) + ->expects($this->at(0)) ->method('addCompilerPass') - ->with($this->isInstanceOf('Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface')); + ->with($this->isInstanceOf($passes[0][0]), $passes[0][1]); + + $container + ->expects($this->at(1)) + ->method('addCompilerPass') + ->with($this->isInstanceOf($passes[1][0])); $bundle = new FOSElasticaBundle(); + $bundle->build($container); } } diff --git a/Tests/Functional/ClientTest.php b/Tests/Functional/ClientTest.php deleted file mode 100644 index 8a6357a..0000000 --- a/Tests/Functional/ClientTest.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * 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 deleted file mode 100644 index a6028b7..0000000 --- a/Tests/Functional/ConfigurationManagerTest.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * 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 ConfigurationManagerTest extends WebTestCase -{ - public function testContainerSource() - { - $client = $this->createClient(array('test_case' => 'Basic')); - $manager = $this->getManager($client); - - $index = $manager->getIndexConfiguration('index'); - - $this->assertEquals('index', $index->getName()); - $this->assertGreaterThanOrEqual(2, count($index->getTypes())); - $this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('type')); - $this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('parent')); - } - - protected function setUp() - { - parent::setUp(); - - $this->deleteTmpDir('Basic'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->deleteTmpDir('Basic'); - } - - /** - * @param Client $client - * - * @return \FOS\ElasticaBundle\Configuration\ConfigManager - */ - private function getManager(Client $client) - { - $manager = $client->getContainer()->get('fos_elastica.config_manager'); - - return $manager; - } -} diff --git a/Tests/Functional/IndexableCallbackTest.php b/Tests/Functional/IndexableCallbackTest.php deleted file mode 100644 index 3f84286..0000000 --- a/Tests/Functional/IndexableCallbackTest.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * 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; - -/** - * @group functional - */ -class IndexableCallbackTest extends WebTestCase -{ - /** - * 2 reasons for this test:. - * - * 1) To test that the configuration rename from is_indexable_callback under the listener - * key is respected, and - * 2) To test the Extension's set up of the Indexable service. - */ - public function testIndexableCallback() - { - $client = $this->createClient(array('test_case' => 'ORM')); - - /** @var \FOS\ElasticaBundle\Provider\Indexable $in */ - $in = $client->getContainer()->get('fos_elastica.indexable'); - - $this->assertTrue($in->isObjectIndexable('index', 'type', new TypeObj())); - $this->assertTrue($in->isObjectIndexable('index', 'type2', new TypeObj())); - $this->assertFalse($in->isObjectIndexable('index', 'type3', new TypeObj())); - $this->assertFalse($in->isObjectIndexable('index', 'type4', new TypeObj())); - } - - protected function setUp() - { - parent::setUp(); - - $this->deleteTmpDir('ORM'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->deleteTmpDir('ORM'); - } -} diff --git a/Tests/Functional/MappingToElasticaTest.php b/Tests/Functional/MappingToElasticaTest.php deleted file mode 100644 index 6f93b7e..0000000 --- a/Tests/Functional/MappingToElasticaTest.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * 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 MappingToElasticaTest extends WebTestCase -{ - public function testResetIndexAddsMappings() - { - $client = $this->createClient(array('test_case' => 'Basic')); - $resetter = $this->getResetter($client); - $resetter->resetIndex('index'); - - $type = $this->getType($client); - $mapping = $type->getMapping(); - - $this->assertNotEmpty($mapping, 'Mapping was populated'); - $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() - { - $client = $this->createClient(array('test_case' => 'Basic')); - $resetter = $this->getResetter($client); - $resetter->resetIndexType('index', 'type'); - - $type = $this->getType($client); - $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']); - } - - public function testORMResetIndexAddsMappings() - { - $client = $this->createClient(array('test_case' => 'ORM')); - $resetter = $this->getResetter($client); - $resetter->resetIndex('index'); - - $type = $this->getType($client); - $mapping = $type->getMapping(); - - $this->assertNotEmpty($mapping, 'Mapping was populated'); - } - - public function testORMResetType() - { - $client = $this->createClient(array('test_case' => 'ORM')); - $resetter = $this->getResetter($client); - $resetter->resetIndexType('index', 'type'); - - $type = $this->getType($client); - $mapping = $type->getMapping(); - - $this->assertNotEmpty($mapping, 'Mapping was populated'); - } - - public function testMappingIteratorToArrayField() - { - $client = $this->createClient(array('test_case' => 'ORM')); - $persister = $client->getContainer()->get('fos_elastica.object_persister.index.type'); - - $object = new TypeObj(); - $object->id = 1; - $object->coll = new \ArrayIterator(array('foo', 'bar')); - $persister->insertOne($object); - - $object->coll = new \ArrayIterator(array('foo', 'bar', 'bazz')); - $object->coll->offsetUnset(1); - - $persister->replaceOne($object); - } - - /** - * @param Client $client - * - * @return \FOS\ElasticaBundle\Resetter $resetter - */ - private function getResetter(Client $client) - { - return $client->getContainer()->get('fos_elastica.resetter'); - } - - /** - * @param Client $client - * @param string $type - * - * @return \Elastica\Type - */ - private function getType(Client $client, $type = 'type') - { - return $client->getContainer()->get('fos_elastica.index.index.'.$type); - } - - protected function setUp() - { - parent::setUp(); - - $this->deleteTmpDir('Basic'); - $this->deleteTmpDir('ORM'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->deleteTmpDir('Basic'); - $this->deleteTmpDir('ORM'); - } -} diff --git a/Tests/Functional/PropertyPathTest.php b/Tests/Functional/PropertyPathTest.php deleted file mode 100644 index 860cb86..0000000 --- a/Tests/Functional/PropertyPathTest.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * 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 deleted file mode 100644 index 81fbc8f..0000000 --- a/Tests/Functional/SerializerTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * 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; - -/** - * @group functional - */ -class SerializerTest extends WebTestCase -{ - public function testMappingIteratorToArrayField() - { - $client = $this->createClient(array('test_case' => 'Serializer')); - $persister = $client->getContainer()->get('fos_elastica.object_persister.index.type'); - - $object = new TypeObj(); - $object->id = 1; - $object->coll = new \ArrayIterator(array('foo', 'bar')); - $persister->insertOne($object); - - $object->coll = new \ArrayIterator(array('foo', 'bar', 'bazz')); - $object->coll->offsetUnset(1); - - $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(); - - $this->deleteTmpDir('Serializer'); - } - - protected function tearDown() - { - parent::tearDown(); - - $this->deleteTmpDir('Serializer'); - } -} diff --git a/Tests/Functional/TypeObj.php b/Tests/Functional/TypeObj.php deleted file mode 100644 index 39e9fe9..0000000 --- a/Tests/Functional/TypeObj.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Tests\Functional; - -class TypeObj -{ - public $id = 5; - public $coll; - public $field1; - public $field2; - - public function isIndexable() - { - return true; - } - - public function isntIndexable() - { - return false; - } - - public function getSerializableColl() - { - return iterator_to_array($this->coll, false); - } -} diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php deleted file mode 100644 index 38f5489..0000000 --- a/Tests/Functional/WebTestCase.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * 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\Tests\Functional\WebTestCase as BaseWebTestCase; - -class WebTestCase extends BaseWebTestCase -{ - protected static function getKernelClass() - { - require_once __DIR__.'/app/AppKernel.php'; - - return 'FOS\ElasticaBundle\Tests\Functional\app\AppKernel'; - } - - protected static function createKernel(array $options = array()) - { - $class = self::getKernelClass(); - - if (!isset($options['test_case'])) { - throw new \InvalidArgumentException('The option "test_case" must be set.'); - } - - return new $class( - $options['test_case'], - isset($options['root_config']) ? $options['root_config'] : 'config.yml', - isset($options['environment']) ? $options['environment'] : 'foselasticabundle'.strtolower($options['test_case']), - isset($options['debug']) ? $options['debug'] : true - ); - } -} diff --git a/Tests/Functional/app/AppKernel.php b/Tests/Functional/app/AppKernel.php deleted file mode 100644 index d75910a..0000000 --- a/Tests/Functional/app/AppKernel.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Tests\Functional\app; - -// get the autoload file -$dir = __DIR__; -$lastDir = null; -while ($dir !== $lastDir) { - $lastDir = $dir; - - if (file_exists($dir.'/autoload.php')) { - require_once $dir.'/autoload.php'; - break; - } - - if (file_exists($dir.'/autoload.php.dist')) { - require_once $dir.'/autoload.php.dist'; - break; - } - - if (file_exists($dir.'/vendor/autoload.php')) { - require_once $dir.'/vendor/autoload.php'; - break; - } - - $dir = dirname($dir); -} - -use Symfony\Component\Config\Loader\LoaderInterface; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\HttpKernel\Kernel; - -/** - * App Test Kernel for functional tests. - * - * @author Johannes M. Schmitt - */ -class AppKernel extends Kernel -{ - private $testCase; - private $rootConfig; - - public function __construct($testCase, $rootConfig, $environment, $debug) - { - if (!is_dir(__DIR__.'/'.$testCase)) { - throw new \InvalidArgumentException(sprintf('The test case "%s" does not exist.', $testCase)); - } - $this->testCase = $testCase; - - $fs = new Filesystem(); - if (!$fs->isAbsolutePath($rootConfig) && !file_exists($rootConfig = __DIR__.'/'.$testCase.'/'.$rootConfig)) { - throw new \InvalidArgumentException(sprintf('The root config "%s" does not exist.', $rootConfig)); - } - $this->rootConfig = $rootConfig; - - parent::__construct($environment, $debug); - } - - public function registerBundles() - { - if (!file_exists($filename = $this->getRootDir().'/'.$this->testCase.'/bundles.php')) { - throw new \RuntimeException(sprintf('The bundles file "%s" does not exist.', $filename)); - } - - return include $filename; - } - - public function init() - { - } - - public function getRootDir() - { - return __DIR__; - } - - public function getCacheDir() - { - return sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$this->testCase.'/cache/'.$this->environment; - } - - public function getLogDir() - { - return sys_get_temp_dir().'/'.Kernel::VERSION.'/'.$this->testCase.'/logs'; - } - - public function registerContainerConfiguration(LoaderInterface $loader) - { - $loader->load($this->rootConfig); - } - - public function serialize() - { - return serialize(array($this->testCase, $this->rootConfig, $this->getEnvironment(), $this->isDebug())); - } - - public function unserialize($str) - { - call_user_func_array(array($this, '__construct'), unserialize($str)); - } - - protected function getKernelParameters() - { - $parameters = parent::getKernelParameters(); - $parameters['kernel.test_case'] = $this->testCase; - - return $parameters; - } -} diff --git a/Tests/Functional/app/Basic/bundles.php b/Tests/Functional/app/Basic/bundles.php deleted file mode 100644 index 7bcaae8..0000000 --- a/Tests/Functional/app/Basic/bundles.php +++ /dev/null @@ -1,13 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Tests\Functional\app\ORM; - -class IndexableService -{ - public function isIndexable() - { - return true; - } - - public static function isntIndexable() - { - return false; - } -} diff --git a/Tests/Functional/app/ORM/bundles.php b/Tests/Functional/app/ORM/bundles.php deleted file mode 100644 index 25db3fe..0000000 --- a/Tests/Functional/app/ORM/bundles.php +++ /dev/null @@ -1,13 +0,0 @@ - - * - * 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 78a3d28..624f64e 100644 --- a/Tests/Index/IndexManagerTest.php +++ b/Tests/Index/IndexManagerTest.php @@ -6,40 +6,40 @@ use FOS\ElasticaBundle\Index\IndexManager; class IndexManagerTest extends \PHPUnit_Framework_TestCase { - private $indexes = array(); - - /** - * @var IndexManager - */ + private $defaultIndexName; + private $indexesByName; + /** @var IndexManager */ private $indexManager; public function setUp() { - foreach (array('index1', 'index2', 'index3') as $indexName) { - $index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index') - ->disableOriginalConstructor() - ->getMock(); + $this->defaultIndexName = 'index2'; + $this->indexesByName = array( + 'index1' => 'test1', + 'index2' => 'test2', + ); - $index->expects($this->any()) - ->method('getName') - ->will($this->returnValue($indexName)); + /** @var $defaultIndex \PHPUnit_Framework_MockObject_MockObject|\Elastica\Index */ + $defaultIndex = $this->getMockBuilder('Elastica\Index') + ->disableOriginalConstructor() + ->getMock(); - $this->indexes[$indexName] = $index; - } + $defaultIndex->expects($this->any()) + ->method('getName') + ->will($this->returnValue($this->defaultIndexName)); - $this->indexManager = new IndexManager($this->indexes, $this->indexes['index2']); + $this->indexManager = new IndexManager($this->indexesByName, $defaultIndex); } public function testGetAllIndexes() { - $this->assertEquals($this->indexes, $this->indexManager->getAllIndexes()); + $this->assertEquals($this->indexesByName, $this->indexManager->getAllIndexes()); } public function testGetIndex() { - $this->assertEquals($this->indexes['index1'], $this->indexManager->getIndex('index1')); - $this->assertEquals($this->indexes['index2'], $this->indexManager->getIndex('index2')); - $this->assertEquals($this->indexes['index3'], $this->indexManager->getIndex('index3')); + $this->assertEquals($this->indexesByName['index1'], $this->indexManager->getIndex('index1')); + $this->assertEquals($this->indexesByName['index2'], $this->indexManager->getIndex('index2')); } /** @@ -47,12 +47,12 @@ class IndexManagerTest extends \PHPUnit_Framework_TestCase */ public function testGetIndexShouldThrowExceptionForInvalidName() { - $this->indexManager->getIndex('index4'); + $this->indexManager->getIndex('index3'); } public function testGetDefaultIndex() { - $this->assertEquals('index2', $this->indexManager->getIndex()->getName()); - $this->assertEquals('index2', $this->indexManager->getDefaultIndex()->getName()); + $this->assertEquals('test2', $this->indexManager->getIndex()); + $this->assertEquals('test2', $this->indexManager->getDefaultIndex()); } } diff --git a/Tests/Index/ResetterTest.php b/Tests/Index/ResetterTest.php index 9b4cd05..7b1c2fa 100644 --- a/Tests/Index/ResetterTest.php +++ b/Tests/Index/ResetterTest.php @@ -5,270 +5,199 @@ 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 { - /** - * @var Resetter - */ - private $resetter; + private $indexConfigsByName; - private $aliasProcessor; - private $configManager; - private $dispatcher; - private $elasticaClient; - private $indexManager; - private $mappingBuilder; + public function setUp() + { + $this->indexConfigsByName = array( + 'foo' => array( + 'index' => $this->getMockElasticaIndex(), + 'config' => array( + 'mappings' => array( + 'a' => array( + 'dynamic_templates' => array(), + 'properties' => array(), + ), + 'b' => array('properties' => array()), + ), + ), + ), + 'bar' => array( + 'index' => $this->getMockElasticaIndex(), + 'config' => array( + 'mappings' => array( + 'a' => array('properties' => array()), + 'b' => array('properties' => array()), + ), + ), + ), + 'parent' => array( + 'index' => $this->getMockElasticaIndex(), + 'config' => array( + 'mappings' => array( + 'a' => array( + 'properties' => array( + 'field_2' => array() + ), + '_parent' => array( + 'type' => 'b', + 'property' => 'b', + 'identifier' => 'id' + ), + ), + 'b' => array('properties' => array()), + ), + ), + ), + ); + } public function testResetAllIndexes() { - $indexName = 'index1'; - $indexConfig = new IndexConfig($indexName, array(), array()); - $this->mockIndex($indexName, $indexConfig); + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['foo']['config'], true); - $this->configManager->expects($this->once()) - ->method('getIndexNames') - ->will($this->returnValue(array($indexName))); + $this->indexConfigsByName['bar']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['bar']['config'], true); - $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(); + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetAllIndexes(); } public function testResetIndex() { - $indexConfig = new IndexConfig('index1', array(), array()); - $this->mockIndex('index1', $indexConfig); + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('create') + ->with($this->indexConfigsByName['foo']['config'], true); - $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->indexConfigsByName['bar']['index']->expects($this->never()) + ->method('create'); - $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'); + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndex('foo'); } /** * @expectedException \InvalidArgumentException */ - public function testFailureWhenMissingIndexDoesntDispatch() + public function testResetIndexShouldThrowExceptionForInvalidIndex() { - $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'); + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndex('baz'); } - public function testResetType() + public function testResetIndexType() { - $typeConfig = new TypeConfig('type', array(), array()); - $this->mockType('type', 'index', $typeConfig); + $type = $this->getMockElasticaType(); - $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->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('getType') + ->with('a') + ->will($this->returnValue($type)); - $this->elasticaClient->expects($this->exactly(2)) - ->method('request') - ->withConsecutive( - array('index/type/', 'DELETE'), - array('index/type/_mapping', 'PUT', array('type' => array()), array()) - ); + $type->expects($this->once()) + ->method('delete'); - $this->resetter->resetIndexType('index', 'type'); + $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['mappings']['a']['properties']); + $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['mappings']['a']['dynamic_templates']); + $type->expects($this->once()) + ->method('setMapping') + ->with($mapping); + + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('foo', 'a'); } /** * @expectedException \InvalidArgumentException */ - public function testNonExistantResetType() + public function testResetIndexTypeShouldThrowExceptionForInvalidIndex() { - $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'); + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('baz', 'a'); } - public function testPostPopulateWithoutAlias() + /** + * @expectedException \InvalidArgumentException + */ + public function testResetIndexTypeShouldThrowExceptionForInvalidType() { - $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'); + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('foo', 'c'); } - public function testPostPopulate() + public function testResetIndexTypeIgnoreTypeMissingException() { - $indexConfig = new IndexConfig('index', array(), array( 'useAlias' => true)); - $index = $this->mockIndex('index', $indexConfig); + $type = $this->getMockElasticaType(); - $this->aliasProcessor->expects($this->once()) - ->method('switchIndexAlias') - ->with($indexConfig, $index); + $this->indexConfigsByName['foo']['index']->expects($this->once()) + ->method('getType') + ->with('a') + ->will($this->returnValue($type)); - $this->resetter->postPopulate('index'); + $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']['mappings']['a']['properties']); + $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['mappings']['a']['dynamic_templates']); + $type->expects($this->once()) + ->method('setMapping') + ->with($mapping); + + $resetter = new Resetter($this->indexConfigsByName); + $resetter->resetIndexType('foo', 'a'); } - private function dispatcherExpects(array $events) + public function testIndexMappingForParent() { - $expectation = $this->dispatcher->expects($this->exactly(count($events))) - ->method('dispatch'); + $type = $this->getMockElasticaType(); - call_user_func_array(array($expectation, 'withConsecutive'), $events); + $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']['mappings']['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'); } - private function mockIndex($indexName, IndexConfig $config, $mapping = array()) + /** + * @return \Elastica\Index + */ + private function getMockElasticaIndex() { - $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->dispatcher = $this->getMockBuilder('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface') - ->getMock(); - $this->elasticaClient = $this->getMockBuilder('Elastica\\Client') - ->disableOriginalConstructor() - ->getMock(); - $this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager') - ->disableOriginalConstructor() - ->getMock(); - $this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder') + return $this->getMockBuilder('Elastica\Index') ->disableOriginalConstructor() ->getMock(); + } - $this->resetter = new Resetter( - $this->configManager, - $this->indexManager, - $this->aliasProcessor, - $this->mappingBuilder, - $this->dispatcher - ); + /** + * @return \Elastica\Type + */ + private function getMockElasticaType() + { + return $this->getMockBuilder('Elastica\Type') + ->disableOriginalConstructor() + ->getMock(); } } diff --git a/Tests/Integration/MappingTest.php b/Tests/Integration/MappingTest.php deleted file mode 100644 index ae7e409..0000000 --- a/Tests/Integration/MappingTest.php +++ /dev/null @@ -1,15 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace FOS\ElasticaBundle\Tests\Integration; - -class MappingTest -{ -} diff --git a/Tests/Logger/ElasticaLoggerTest.php b/Tests/Logger/ElasticaLoggerTest.php index 7d90639..96adf53 100644 --- a/Tests/Logger/ElasticaLoggerTest.php +++ b/Tests/Logger/ElasticaLoggerTest.php @@ -22,8 +22,7 @@ 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) @@ -46,7 +45,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 71bb076..8849035 100644 --- a/Tests/Manager/RepositoryManagerTest.php +++ b/Tests/Manager/RepositoryManagerTest.php @@ -4,19 +4,16 @@ 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 06039f0..497c286 100644 --- a/Tests/Persister/ObjectPersisterTest.php +++ b/Tests/Persister/ObjectPersisterTest.php @@ -31,6 +31,13 @@ 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(); @@ -40,7 +47,10 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $typeMock->expects($this->once()) - ->method('updateDocuments'); + ->method('deleteById') + ->with($this->equalTo(123)); + $typeMock->expects($this->once()) + ->method('addDocument'); $fields = array('name' => array()); @@ -81,7 +91,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase $typeMock->expects($this->never()) ->method('deleteById'); $typeMock->expects($this->once()) - ->method('addDocuments'); + ->method('addDocument'); $fields = array('name' => array()); @@ -120,7 +130,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $typeMock->expects($this->once()) - ->method('deleteDocuments'); + ->method('deleteById'); $typeMock->expects($this->never()) ->method('addDocument'); @@ -203,7 +213,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase private function getTransformer() { $transformer = new ModelToElasticaAutoTransformer(); - $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor()); + $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); return $transformer; } diff --git a/Tests/Persister/ObjectSerializerPersisterTest.php b/Tests/Persister/ObjectSerializerPersisterTest.php index 0536d06..aae3a64 100644 --- a/Tests/Persister/ObjectSerializerPersisterTest.php +++ b/Tests/Persister/ObjectSerializerPersisterTest.php @@ -2,7 +2,9 @@ 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; @@ -24,6 +26,13 @@ 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(); @@ -33,7 +42,10 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $typeMock->expects($this->once()) - ->method('updateDocuments'); + ->method('deleteById') + ->with($this->equalTo(123)); + $typeMock->expects($this->once()) + ->method('addDocument'); $serializerMock = $this->getMockBuilder('FOS\ElasticaBundle\Serializer\Callback')->getMock(); $serializerMock->expects($this->once())->method('serialize'); @@ -53,7 +65,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase $typeMock->expects($this->never()) ->method('deleteById'); $typeMock->expects($this->once()) - ->method('addDocuments'); + ->method('addDocument'); $serializerMock = $this->getMockBuilder('FOS\ElasticaBundle\Serializer\Callback')->getMock(); $serializerMock->expects($this->once())->method('serialize'); @@ -71,7 +83,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $typeMock->expects($this->once()) - ->method('deleteDocuments'); + ->method('deleteById'); $typeMock->expects($this->never()) ->method('addDocument'); @@ -112,7 +124,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase private function getTransformer() { $transformer = new ModelToElasticaIdentifierTransformer(); - $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor()); + $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); return $transformer; } diff --git a/Tests/Provider/IndexableTest.php b/Tests/Provider/IndexableTest.php deleted file mode 100644 index e122ec1..0000000 --- a/Tests/Provider/IndexableTest.php +++ /dev/null @@ -1,123 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace FOS\ElasticaBundle\Tests\Provider; - -use FOS\ElasticaBundle\Provider\Indexable; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; - -class IndexableTest extends \PHPUnit_Framework_TestCase -{ - public $container; - - public function testIndexableUnknown() - { - $indexable = new Indexable(array(), $this->container); - $index = $indexable->isObjectIndexable('index', 'type', new Entity()); - - $this->assertTrue($index); - } - - /** - * @dataProvider provideIsIndexableCallbacks - */ - public function testValidIndexableCallbacks($callback, $return) - { - $indexable = new Indexable(array( - 'index/type' => $callback, - ), $this->container); - $index = $indexable->isObjectIndexable('index', 'type', new Entity()); - - $this->assertEquals($return, $index); - } - - /** - * @dataProvider provideInvalidIsIndexableCallbacks - * @expectedException \InvalidArgumentException - */ - public function testInvalidIsIndexableCallbacks($callback) - { - $indexable = new Indexable(array( - 'index/type' => $callback, - ), $this->container); - $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()'), - ); - } - - public function provideIsIndexableCallbacks() - { - return array( - array('isIndexable', false), - array(array(new IndexableDecider(), 'isIndexable'), true), - array(array('@indexableService', 'isIndexable'), 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) - ); - } - - protected function setUp() - { - $this->container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerInterface') - ->getMock(); - - $this->container->expects($this->any()) - ->method('get') - ->with('indexableService') - ->will($this->returnValue(new IndexableDecider())); - } -} - -class Entity -{ - public $property = 'abc'; - - public function isIndexable() - { - return false; - } - - public function maybeIndex() - { - return true; - } -} - -class IndexableDecider -{ - public function isIndexable(Entity $entity) - { - return !$entity->isIndexable(); - } - - protected function internalMethod() - { - } - - public function __invoke($object) - { - return true; - } -} diff --git a/Tests/RepositoryTest.php b/Tests/RepositoryTest.php index 7702af2..c4d4efc 100644 --- a/Tests/RepositoryTest.php +++ b/Tests/RepositoryTest.php @@ -13,7 +13,14 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase { $testQuery = 'Test Query'; - $finderMock = $this->getFinderMock($testQuery); + /** @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)); + $repository = new Repository($finderMock); $repository->find($testQuery); } @@ -23,7 +30,14 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $testQuery = 'Test Query'; $testLimit = 20; - $finderMock = $this->getFinderMock($testQuery, $testLimit); + /** @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)); + $repository = new Repository($finderMock); $repository->find($testQuery, $testLimit); } @@ -32,7 +46,14 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase { $testQuery = 'Test Query'; - $finderMock = $this->getFinderMock($testQuery, array(), 'findPaginated'); + /** @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)); + $repository = new Repository($finderMock); $repository->findPaginated($testQuery); } @@ -41,7 +62,14 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase { $testQuery = 'Test Query'; - $finderMock = $this->getFinderMock($testQuery, array(), 'createPaginatorAdapter'); + /** @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)); + $repository = new Repository($finderMock); $repository->createPaginatorAdapter($testQuery); } @@ -49,28 +77,17 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase public function testThatFindHybridCallsFindHybridOnFinder() { $testQuery = 'Test Query'; + $testLimit = 20; - $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') - { + /** @var $typeMock \PHPUnit_Framework_MockObject_MockObject|\FOS\ElasticaBundle\Finder\TransformedFinder */ $finderMock = $this->getMockBuilder('FOS\ElasticaBundle\Finder\TransformedFinder') ->disableOriginalConstructor() ->getMock(); $finderMock->expects($this->once()) - ->method($method) + ->method('findHybrid') ->with($this->equalTo($testQuery), $this->equalTo($testLimit)); - return $finderMock; + $repository = new Repository($finderMock); + $repository->findHybrid($testQuery, $testLimit); } } diff --git a/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php b/Tests/Transformer/ElasticaToModelTransformerCollectionTest.php index 56a7200..eb4d8e4 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,9 +157,6 @@ 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 f45134e..1fa6a8e 100644 --- a/Tests/Transformer/ModelToElasticaAutoTransformerTest.php +++ b/Tests/Transformer/ModelToElasticaAutoTransformerTest.php @@ -2,7 +2,6 @@ namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer; -use FOS\ElasticaBundle\Event\TransformEvent; use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -22,8 +21,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() @@ -48,7 +47,7 @@ class POPO { return array( 'key1' => 'value1', - 'key2' => 'value2', + 'key2' => 'value2' ); } @@ -110,7 +109,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() @@ -126,33 +125,11 @@ class POPO class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase { - public function testTransformerDispatches() + public function setUp() { - $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')); + if (!class_exists('Elastica\Document')) { + $this->markTestSkipped('The Elastica library classes are not available'); + } } public function testThatCanTransformObject() @@ -175,7 +152,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase 'float' => array(), 'bool' => array(), 'date' => array(), - 'falseBool' => array(), + 'falseBool' => array() ) ); $data = $document->getData(); @@ -208,7 +185,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase $this->assertEquals( array( 'key1' => 'value1', - 'key2' => 'value2', + 'key2' => 'value2' ), $data['array'] ); } @@ -253,7 +230,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() @@ -263,7 +240,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'] ); } @@ -271,18 +248,18 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase { $transformer = $this->getTransformer(); $document = $transformer->transform(new POPO(), array( - 'sub' => array( - 'type' => 'nested', - 'properties' => array('foo' => array()), - ), - )); + 'sub' => array( + 'type' => 'nested', + 'properties' => array('foo' => '~') + ) + )); $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']); } @@ -292,8 +269,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(); @@ -301,7 +278,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']); } @@ -310,18 +287,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() @@ -336,14 +313,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(); @@ -356,14 +333,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] ); @@ -373,7 +350,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()); @@ -383,7 +360,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()); @@ -393,7 +370,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()); @@ -403,21 +380,19 @@ 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($dispatcher = null) + private function getTransformer() { - $transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher); - $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor()); + $transformer = new ModelToElasticaAutoTransformer(); + $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); return $transformer; } diff --git a/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php b/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php index aa3d7b7..f1a77d4 100644 --- a/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php +++ b/Tests/Transformer/ModelToElasticaIdentifierTransformerTest.php @@ -23,6 +23,13 @@ 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(); @@ -51,7 +58,7 @@ class ModelToElasticaIdentifierTransformerTest extends \PHPUnit_Framework_TestCa private function getTransformer() { $transformer = new ModelToElasticaIdentifierTransformer(); - $transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor()); + $transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor()); return $transformer; } diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php new file mode 100644 index 0000000..30fb165 --- /dev/null +++ b/Tests/bootstrap.php @@ -0,0 +1,19 @@ + - * - * 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/CombinedResultTransformer.php b/Transformer/CombinedResultTransformer.php new file mode 100644 index 0000000..50822ae --- /dev/null +++ b/Transformer/CombinedResultTransformer.php @@ -0,0 +1,71 @@ + + */ +class CombinedResultTransformer +{ + /** + * @var \FOS\ElasticaBundle\Type\TypeConfiguration + */ + private $configurations; + + /** + * @var ResultTransformerInterface + */ + private $transformer; + + /** + * @param \FOS\ElasticaBundle\Type\TypeConfiguration[] $configurations + * @param ResultTransformerInterface $transformer + */ + public function __construct(array $configurations, ResultTransformerInterface $transformer) + { + $this->configurations = $configurations; + $this->transformer = $transformer; + } + + /** + * Transforms Elastica results into Models. + * + * @param TransformingResult[] $results + * @return object[] + */ + public function transform($results) + { + $grouped = array(); + + foreach ($results as $result) { + $grouped[$result->getType()][] = $result; + } + + foreach ($grouped as $type => $group) { + $this->transformer->transform($this->getConfiguration($type), $group); + } + } + + /** + * Retrieves the transformer for a given type. + * + * @param string $type + * @return \FOS\ElasticaBundle\Type\TypeConfiguration + * @throws \InvalidArgumentException + */ + private function getConfiguration($type) + { + if (!array_key_exists($type, $this->configurations)) { + throw new \InvalidArgumentException(sprintf( + 'Configuration for type "%s" is not registered with this combined transformer.', + $type + )); + } + + return $this->configurations[$type]; + } +} diff --git a/Transformer/ElasticaToModelTransformerCollection.php b/Transformer/ElasticaToModelTransformerCollection.php index 9920f43..f65f8db 100644 --- a/Transformer/ElasticaToModelTransformerCollection.php +++ b/Transformer/ElasticaToModelTransformerCollection.php @@ -41,7 +41,6 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer /** * @param Document[] $elasticaObjects - * * @return array */ public function transform(array $elasticaObjects) @@ -52,12 +51,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 @@ -81,7 +80,7 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer $objects = $this->transform($elasticaObjects); $result = array(); - for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) { + for ($i = 0; $i < count($elasticaObjects); $i++) { $result[] = new HybridResult($elasticaObjects[$i], $objects[$i]); } diff --git a/Transformer/ElasticaToModelTransformerInterface.php b/Transformer/ElasticaToModelTransformerInterface.php index 71cd651..5635ef3 100644 --- a/Transformer/ElasticaToModelTransformerInterface.php +++ b/Transformer/ElasticaToModelTransformerInterface.php @@ -3,33 +3,32 @@ 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 **/ - public function transform(array $elasticaObjects); + function transform(array $elasticaObjects); - public function hybridTransform(array $elasticaObjects); + function hybridTransform(array $elasticaObjects); /** * Returns the object class used by the transformer. * * @return string */ - public function getObjectClass(); + function getObjectClass(); /** - * Returns the identifier field from the options. + * Returns the identifier field from the options * * @return string the identifier field */ - public function getIdentifierField(); + function getIdentifierField(); } diff --git a/Transformer/HighlightableModelInterface.php b/Transformer/HighlightableModelInterface.php index 96c6c7c..d55407e 100644 --- a/Transformer/HighlightableModelInterface.php +++ b/Transformer/HighlightableModelInterface.php @@ -1,23 +1,16 @@ - 'id', + 'identifier' => 'id' ); /** - * PropertyAccessor instance. + * PropertyAccessor instance * * @var PropertyAccessorInterface */ protected $propertyAccessor; /** - * Instanciates a new Mapper. + * Instanciates a new Mapper * - * @param array $options - * @param EventDispatcherInterface $dispatcher + * @param array $options */ - public function __construct(array $options = array(), EventDispatcherInterface $dispatcher = null) + public function __construct(array $options = array()) { $this->options = array_merge($this->options, $options); - $this->dispatcher = $dispatcher; } /** - * Set the PropertyAccessor. + * Set the PropertyAccessor * * @param PropertyAccessorInterface $propertyAccessor */ @@ -58,7 +49,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 @@ -72,27 +63,19 @@ 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; } - $path = isset($mapping['property_path']) ? - $mapping['property_path'] : - $key; - if (false === $path) { - continue; - } - $value = $this->propertyAccessor->getValue($object, $path); + $value = $this->propertyAccessor->getValue($object, $key); 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; } @@ -103,28 +86,20 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf } else { $document->addFileContent($key, $value); } - continue; } $document->set($key, $this->normalizeValue($value)); } - if ($this->dispatcher) { - $event = new TransformEvent($document, $fields, $object); - $this->dispatcher->dispatch(TransformEvent::POST_TRANSFORM, $event); - - $document = $event->getDocument(); - } - return $document; } /** - * 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 */ @@ -148,7 +123,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 * @@ -156,16 +131,17 @@ 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; } }; if (is_array($value) || $value instanceof \Traversable || $value instanceof \ArrayAccess) { - $value = is_array($value) ? $value : iterator_to_array($value, false); + $value = is_array($value) ? $value : iterator_to_array($value); array_walk_recursive($value, $normalizeValue); } else { $normalizeValue($value); diff --git a/Transformer/ModelToElasticaIdentifierTransformer.php b/Transformer/ModelToElasticaIdentifierTransformer.php index 6301be1..654850f 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 @@ -21,7 +21,6 @@ class ModelToElasticaIdentifierTransformer extends ModelToElasticaAutoTransforme public function transform($object, array $fields) { $identifier = $this->propertyAccessor->getValue($object, $this->options['identifier']); - return new Document($identifier); } } diff --git a/Transformer/ModelToElasticaTransformerInterface.php b/Transformer/ModelToElasticaTransformerInterface.php index 0ad9f12..ec9ada3 100644 --- a/Transformer/ModelToElasticaTransformerInterface.php +++ b/Transformer/ModelToElasticaTransformerInterface.php @@ -3,17 +3,16 @@ 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 **/ - public function transform($object, array $fields); + function transform($object, array $fields); } diff --git a/Transformer/ResultTransformer.php b/Transformer/ResultTransformer.php new file mode 100644 index 0000000..31a4b9f --- /dev/null +++ b/Transformer/ResultTransformer.php @@ -0,0 +1,79 @@ +lookupManager = $lookupManager; + $this->propertyAccessor = $propertyAccessor; + } + + /** + * Transforms Elastica results into Models. + * + * @param TypeConfiguration $configuration + * @param \FOS\ElasticaBundle\Elastica\TransformingResult[] $results + * @throws \FOS\ElasticaBundle\Exception\MissingModelException + * @throws \FOS\ElasticaBundle\Exception\UnexpectedObjectException + */ + public function transform(TypeConfiguration $configuration, $results) + { + $results = $this->processResults($results); + $lookup = $this->lookupManager->getLookup($configuration->getType()); + $objects = $lookup->lookup($configuration, array_keys($results)); + + if (!$configuration->isIgnoreMissing() and count($objects) < count($results)) { + throw new MissingModelException(count($objects), count($results)); + } + + $identifierProperty = $configuration->getIdentifierProperty(); + foreach ($objects as $object) { + $id = $this->propertyAccessor->getValue($object, $identifierProperty); + + if (!array_key_exists($id, $results)) { + throw new UnexpectedObjectException($id); + } + + $results[$id]->setTransformed($object); + } + } + + /** + * Processes the results array into a more usable format for the transformation. + * + * @param \FOS\ElasticaBundle\Elastica\TransformingResult[] $results + * @return \FOS\ElasticaBundle\Elastica\TransformingResult[] + */ + private function processResults($results) + { + $sorted = array(); + foreach ($results as $result) { + $sorted[$result->getId()] = $result; + } + + return $sorted; + } +} diff --git a/Transformer/ResultTransformerInterface.php b/Transformer/ResultTransformerInterface.php new file mode 100644 index 0000000..ebce242 --- /dev/null +++ b/Transformer/ResultTransformerInterface.php @@ -0,0 +1,16 @@ + + */ +interface LookupInterface +{ + /** + * Returns the lookup key. + * + * @return string + */ + public function getKey(); + + /** + * Look up objects of a specific type with ids as supplied. + * + * @param TypeConfiguration $configuration + * @param int[] $ids + * @return object[] + */ + public function lookup(TypeConfiguration $configuration, array $ids); +} diff --git a/Type/LookupManager.php b/Type/LookupManager.php new file mode 100644 index 0000000..15a7dc6 --- /dev/null +++ b/Type/LookupManager.php @@ -0,0 +1,35 @@ +lookups[$lookup->getKey()] = $lookup; + } + } + + /** + * @param string $type + * @return LookupInterface + * @throws \InvalidArgumentException + */ + public function getLookup($type) + { + if (!array_key_exists($type, $this->lookups)) { + throw new \InvalidArgumentException(sprintf('Lookup with key "%s" does not exist', $type)); + } + + return $this->lookups[$type]; + } +} diff --git a/Type/TypeConfiguration.php b/Type/TypeConfiguration.php new file mode 100644 index 0000000..6a9ff0d --- /dev/null +++ b/Type/TypeConfiguration.php @@ -0,0 +1,102 @@ + + */ +final class TypeConfiguration +{ + /** + * The identifier property that is used to retrieve an identifier from the model. + * + * @var string + */ + private $identifierProperty; + + /** + * Returns the fully qualified class for the model that this type represents. + * + * @var string + */ + private $modelClass; + + /** + * Returns the repository method that will create a query builder or associated + * query object for lookup purposes. + * + * @var string + */ + private $repositoryMethod; + + /** + * Returns the name of the type. + * + * @var string + */ + private $type; + + /** + * If the lookup should hydrate models to objects or leave data as an array. + * + * @var bool + */ + private $hydrate = true; + + /** + * If the type should ignore missing results from a lookup. + * + * @var bool + */ + private $ignoreMissing = false; + + /** + * @return boolean + */ + public function isHydrate() + { + return $this->hydrate; + } + + /** + * @return string + */ + public function getIdentifierProperty() + { + return $this->identifierProperty; + } + + /** + * @return boolean + */ + public function isIgnoreMissing() + { + return $this->ignoreMissing; + } + + /** + * @return string + */ + public function getModelClass() + { + return $this->modelClass; + } + + /** + * @return string + */ + public function getRepositoryMethod() + { + return $this->repositoryMethod; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/composer.json b/composer.json index 9705a04..e827ac6 100644 --- a/composer.json +++ b/composer.json @@ -6,39 +6,43 @@ "homepage": "https://github.com/FriendsOfSymfony/FOSElasticaBundle", "license": "MIT", "authors": [ - { "name": "FriendsOfSymfony Community", "homepage": "https://github.com/FriendsOfSymfony/FOSElasticaBundle/contributors" }, - { "name": "Tim Nagel", "email": "tim@nagel.com.au" }, + { "name": "Thibault Duplessis", "email": "thibault.duplessis@gmail.com" }, { "name": "Richard Miller", "email": "richard.miller@limethinking.co.uk" }, { "name": "Jeremy Mikola", "email": "jmikola@gmail.com" } ], "require": { "php": ">=5.3.2", + "doctrine/inflector": "~1.0", "symfony/framework-bundle": "~2.3", "symfony/console": "~2.1", "symfony/form": "~2.1", "symfony/property-access": "~2.2", - "ruflin/elastica": ">=0.90.10.0, <1.5-dev", + "ruflin/elastica": ">=0.90.2.0, <1.1-dev", "psr/log": "~1.0" }, "require-dev":{ - "doctrine/orm": "~2.4", - "doctrine/doctrine-bundle": "~1.2", - "jms/serializer-bundle": "@stable", - "phpunit/phpunit": "~4.1", + "doctrine/orm": ">=2.2,<2.5-dev", + "doctrine/mongodb-odm": "1.0.*@dev", "propel/propel1": "1.6.*", - "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" + "pagerfanta/pagerfanta": "1.0.*@dev", + "knplabs/knp-components": "1.2.*", + "symfony/expression-language" : "2.4.*@dev" + }, + "suggest": { + "doctrine/orm": ">=2.2,<2.5-dev", + "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.*@dev" }, "autoload": { - "psr-4": { "FOS\\ElasticaBundle\\": "" } + "psr-0": { "FOS\\ElasticaBundle": "" } }, + "target-dir": "FOS/ElasticaBundle", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "3.0.x-dev" } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 799d5bf..005cfd8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ - + ./Tests