Compare commits

..

No commits in common. "master" and "2.0.x" have entirely different histories.

169 changed files with 2693 additions and 9660 deletions

View file

@ -1,5 +0,0 @@
imports:
- php
tools:
external_code_coverage: true

View file

@ -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;'
- 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
- echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- composer install --dev

View file

@ -1,16 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Annotation;
/**
* Annotation class for setting search repository.
*
* @author Richard Miller <info@limethinking.co.uk>
* @Annotation
* @Target("CLASS")
*/
class Search
{
/** @var string */
public $repositoryClass;
}

View file

@ -1,22 +0,0 @@
CHANGELOG for 2.1.x
===================
This changelog references the relevant changes (bug and security fixes) done
in 2.1 minor 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/v2.1.0...v2.1.1
To generate a changelog summary since the last version, run
`git log --no-merges --oneline v2.1.0...2.1.x`
* 2.1.2 (2013-06-06)
* 00e9a49: Allow Symfony dependencies until 3.0
* 2.1.1 (2013-05-15)
* c05e0ca: Added documentation for ignoring missing hits
* 00b67fd: Ignore missing index hits

View file

@ -1,76 +0,0 @@
CHANGELOG for 3.0.x
===================
This changelog references the relevant changes (bug and security fixes) done
in 3.0 minor 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.0...v3.0.1
To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x`
* 3.0.9 (2015-03-12)
* Fix a bug in the BC layer of the type configuration for empty configs
* Fix the service definition for the Doctrine listener when the logger is not enabled
* 3.0.8 (2014-01-31)
* Fixed handling of empty indexes #760
* Added support for `connectionStrategy` Elastica configuration #732
* Allow Elastica 1.4
* 3.0.7 (2015-01-21)
* Fixed the indexing of parent/child relations, broken since 3.0 #774
* Fixed multi_field properties not being normalised #769
* 3.0.6 (2015-01-04)
* Removed unused public image asset for the web development toolbar #742
* Fixed is_indexable_callback BC code to support array notation #761
* Fixed debug_logger for type providers #724
* Clean the OM if we filter away the entire batch #737
* 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called
`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\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)
* a9c4c93: Logger is now only enabled in debug mode by default
* #463: allowing hot swappable reindexing
* #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)
* 41bf07e: Renamed the `no-stop-on-error` option in PopulateCommand to `ignore-errors`
* 418b9d7: Fixed validation of url configuration
* 726892c: Ignore TypeMissingException when resetting a single type. This allows to create new types without having to recreate the whole index.
* 7f53bad Add support for include_in_{parent,root} for nested and objects

View file

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

View file

@ -2,11 +2,34 @@
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\Elastica\Client as BaseClient;
use Elastica_Client;
use FOS\ElasticaBundle\Logger\ElasticaLogger;
/**
* @deprecated Use \FOS\ElasticaBundle\Elastica\LoggingClient
* @author Gordon Franke <info@nevalon.de>
*/
class Client extends BaseClient
class Client extends Elastica_Client
{
/**
* @var ElasticaLogger
*/
protected $logger;
public function setLogger(ElasticaLogger $logger)
{
$this->logger = $logger;
}
public function request($path, $method, $data = array(), array $query = array())
{
$start = microtime(true);
$response = parent::request($path, $method, $data, $query);
if (null !== $this->logger) {
$time = microtime(true) - $start;
$this->logger->logQuery($path, $method, $data, $time);
}
return $response;
}
}

129
Command/PopulateCommand.php Normal file → Executable file
View file

@ -2,38 +2,27 @@
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\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\Output;
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
*/
@ -54,58 +43,28 @@ class PopulateCommand extends ContainerAwareCommand
->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to repopulate')
->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type to repopulate')
->addOption('no-reset', null, InputOption::VALUE_NONE, 'Do not reset index before populating')
->addOption('offset', null, InputOption::VALUE_REQUIRED, 'Start indexing at offset', 0)
->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');
}
if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */
$dialog = $this->getHelperSet()->get('dialog');
if (!$dialog->askConfirmation($output, '<question>You chose to reset the index and start indexing with an offset. Do you really want to do that?</question>', true)) {
return;
}
}
$index = $input->getOption('index');
$type = $input->getOption('type');
$reset = $input->getOption('no-reset') ? false : true;
if (null === $index && null !== $type) {
throw new \InvalidArgumentException('Cannot specify type option without an index.');
@ -113,15 +72,15 @@ class PopulateCommand extends ContainerAwareCommand
if (null !== $index) {
if (null !== $type) {
$this->populateIndexType($output, $index, $type, $reset, $options);
$this->populateIndexType($output, $index, $type, $reset);
} else {
$this->populateIndex($output, $index, $reset, $options);
$this->populateIndex($output, $index, $reset);
}
} else {
$indexes = array_keys($this->indexManager->getAllIndexes());
foreach ($indexes as $index) {
$this->populateIndex($output, $index, $reset, $options);
$this->populateIndex($output, $index, $reset);
}
}
}
@ -132,26 +91,27 @@ class PopulateCommand extends ContainerAwareCommand
* @param OutputInterface $output
* @param string $index
* @param boolean $reset
* @param array $options
*/
private function populateIndex(OutputInterface $output, $index, $reset, $options)
private function populateIndex(OutputInterface $output, $index, $reset)
{
$event = new IndexPopulateEvent($index, $reset, $options);
$this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event);
if ($event->isReset()) {
if ($reset) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $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('<info>Populating</info> %s/%s, %s', $index, $type, $message));
};
$provider->populate($loggerClosure);
}
$this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);
$this->refreshIndex($output, $index);
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->indexManager->getIndex($index)->refresh();
}
/**
@ -161,41 +121,22 @@ class PopulateCommand extends ContainerAwareCommand
* @param string $index
* @param string $type
* @param boolean $reset
* @param array $options
*/
private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options)
private function populateIndexType(OutputInterface $output, $index, $type, $reset)
{
$event = new TypePopulateEvent($index, $type, $reset, $options);
$this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event);
if ($event->isReset()) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
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());
$provider->populate($loggerClosure);
$this->dispatcher->dispatch(TypePopulateEvent::POST_TYPE_POPULATE, $event);
$this->refreshIndex($output, $index, false);
}
/**
* Refreshes an index.
*
* @param OutputInterface $output
* @param string $index
* @param bool $postPopulate
*/
private function refreshIndex(OutputInterface $output, $index, $postPopulate = true)
{
if ($postPopulate) {
$this->resetter->postPopulate($index);
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$output->writeln(sprintf('Refreshing: %s', $index));
$this->indexManager->getIndex($index)->refresh();
}
}

View file

@ -1,103 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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('<info>%s</info> <error>%s</error>', $action, $message));
$progress->display();
}
$progress->setMessage(sprintf('<info>%s</info> <comment>%s/%s</comment>', $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('<info>%s</info> <error>%s</error>', $action, $message));
}
$currentTime = microtime(true);
$timeDifference = $currentTime - $lastStep;
$objectsPerSecond = $lastStep ? ($increment / $timeDifference) : $increment;
$lastStep = $currentTime;
$current += $increment;
$percent = 100 * $current / $totalObjects;
$output->writeln(sprintf(
'<info>%s</info> <comment>%s/%s</comment> %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))
));
};
}
}

View file

@ -1,78 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Resetter;
/**
* Reset search indexes.
*/
class ResetCommand extends ContainerAwareCommand
{
/**
* @var IndexManager
*/
private $indexManager;
/**
* @var Resetter
*/
private $resetter;
/**
* @see Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this
->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')
;
}
/**
* @see Symfony\Component\Console\Command\Command::initialize()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->indexManager = $this->getContainer()->get('fos_elastica.index_manager');
$this->resetter = $this->getContainer()->get('fos_elastica.resetter');
}
/**
* @see Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$index = $input->getOption('index');
$type = $input->getOption('type');
$force = (bool) $input->getOption('force');
if (null === $index && null !== $type) {
throw new \InvalidArgumentException('Cannot specify type option without an index.');
}
if (null !== $type) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
$this->resetter->resetIndexType($index, $type);
} else {
$indexes = null === $index
? array_keys($this->indexManager->getAllIndexes())
: array($index)
;
foreach ($indexes as $index) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
$this->resetter->resetIndex($index, false, $force);
}
}
}
}

View file

@ -7,11 +7,12 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Elastica\Query;
use Elastica\Result;
use Symfony\Component\Console\Output\Output;
use Elastica_Query;
use Elastica_Result;
/**
* Searches a type.
* Searches a type
*/
class SearchCommand extends ContainerAwareCommand
{
@ -40,11 +41,11 @@ class SearchCommand extends ContainerAwareCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$indexName = $input->getOption('index');
/** @var $index \Elastica\Index */
/** @var $index \Elastica_Index */
$index = $this->getContainer()->get('fos_elastica.index_manager')->getIndex($indexName ? $indexName : null);
$type = $index->getType($input->getArgument('type'));
$query = Query::create($input->getArgument('query'));
$query->setSize($input->getOption('limit'));
$query = Elastica_Query::create($input->getArgument('query'));
$query->setLimit($input->getOption('limit'));
if ($input->getOption('explain')) {
$query->setExplain(true);
}
@ -57,7 +58,7 @@ class SearchCommand extends ContainerAwareCommand
}
}
protected function formatResult(Result $result, $showField, $showSource, $showId, $explain)
protected function formatResult(Elastica_Result $result, $showField, $showSource, $showId, $explain)
{
$source = $result->getSource();
if ($showField) {

View file

@ -1,64 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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]);
}
}

View file

@ -1,124 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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;
}
}

View file

@ -1,44 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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);
}

View file

@ -1,26 +1,18 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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 <info@limethinking.co.uk>
* @Annotation
*
* @deprecated Use FOS\ElasticaBundle\Annotation\Search instead
* @Target("CLASS")
*/
class Search extends BaseSearch
class Search
{
/** @var string */
public $repositoryClass;
}

View file

@ -1,80 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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;
}
}

View file

@ -1,26 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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();
}

View file

@ -1,113 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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;
}
}

View file

@ -1,36 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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);
}
}

View file

@ -1,38 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* 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);
}
}

View file

@ -55,7 +55,6 @@ class RegisterProvidersPass implements CompilerPassInterface
* Returns whether the class implements ProviderInterface.
*
* @param string $class
*
* @return boolean
*/
private function isProviderImplementation($class)

View file

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

View file

@ -8,29 +8,18 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* Stores supported database drivers.
*
* @var array
*/
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)
{
$this->debug = $debug;
public function __construct($configArray){
$this->configArray = $configArray;
}
/**
* Generates the configuration tree.
*
* @return TreeBuilder
* @return \Symfony\Component\Config\Definition\NodeInterface
*/
public function getConfigTreeBuilder()
{
@ -42,20 +31,9 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('default_client')
->info('Defaults to the first client defined')
->end()
->scalarNode('default_index')
->info('Defaults to the first index defined')
->end()
->scalarNode('default_client')->end()
->scalarNode('default_index')->end()
->scalarNode('default_manager')->defaultValue('orm')->end()
->arrayNode('serializer')
->treatNullLike(array())
->children()
->scalarNode('callback_class')->defaultValue('FOS\ElasticaBundle\Serializer\Callback')->end()
->scalarNode('serializer')->defaultValue('serializer')->end()
->end()
->end()
->end()
;
@ -63,7 +41,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,67 +62,43 @@ 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'],
)
)
);
})
->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'],
)
)
);
})
->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.'/'; })
->end()
->end()
->scalarNode('url')->end()
->scalarNode('host')->end()
->scalarNode('port')->end()
->scalarNode('proxy')->end()
->scalarNode('logger')
->defaultValue($this->debug ? 'fos_elastica.logger' : false)
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
->end()
->arrayNode('headers')
->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 +107,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Adds the configuration for the "indexes" key.
* Adds the configuration for the "indexes" key
*/
private function addIndexesSection(ArrayNodeDefinition $rootNode)
{
@ -153,11 +117,9 @@ class Configuration implements ConfigurationInterface
->arrayNode('indexes')
->useAttributeAsKey('name')
->prototype('array')
->performNoDeepMerging()
->children()
->scalarNode('index_name')
->info('Defaults to the name of the index, but can be modified if the index name is different in ElasticSearch')
->end()
->booleanNode('use_alias')->defaultValue(false)->end()
->scalarNode('index_name')->end()
->scalarNode('client')->end()
->scalarNode('finder')
->treatNullLike(true)
@ -167,8 +129,58 @@ 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('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('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,130 +204,68 @@ 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()
->scalarNode('index_analyzer')->end()
->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end()
->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode())
->append($this->getSerializerNode())
->end()
->append($this->getIdNode())
->append($this->getPropertiesNode())
->append($this->getDynamicTemplateNode())
->append($this->getSourceNode())
->append($this->getBoostNode())
->append($this->getRoutingNode())
->append($this->getParentNode())
->append($this->getAllNode())
->append($this->getTimestampNode())
->append($this->getTtlNode())
->end()
;
return $node;
}
/**
* Returns the array node used for "properties".
*/
protected function getPropertiesNode()
{
$builder = new TreeBuilder();
$node = $builder->root('properties');
$node
->useAttributeAsKey('name')
->prototype('variable')
->treatNullLike(array());
return $node;
}
/**
* Returns the array node used for "dynamic_templates".
*/
public function getDynamicTemplateNode()
{
$builder = new TreeBuilder();
$node = $builder->root('dynamic_templates');
$node
->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())
->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()
->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('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->end()
->append($this->getMappingsNode())
->append($this->getSourceNode())
->append($this->getBoostNode())
->append($this->getRoutingNode())
->end()
;
@ -323,22 +273,150 @@ class Configuration implements ConfigurationInterface
}
/**
* Returns the array node used for "_id".
* Returns the array node used for "mappings".
*/
protected function getIdNode()
protected function getMappingsNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_id');
$node = $builder->root('mappings');
$node
->children()
->scalarNode('path')->end()
->end()
;
$nestings = $this->getNestings();
$childrenNode = $node
->useAttributeAsKey('name')
->prototype('array')
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
$this->addFieldConfig($childrenNode, $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();
if (isset($nestings['fields'])) {
$this->addNestedFieldConfig($node, $nestings, 'fields');
}
if (isset($nestings['properties'])) {
$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')
->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) {
$nestings = array_merge_recursive($nestings, $this->getNestingsForType($type['mappings'], $nestings));
}
}
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 "_source".
*/
@ -359,7 +437,7 @@ class Configuration implements ConfigurationInterface
->end()
->scalarNode('compress')->end()
->scalarNode('compress_threshold')->end()
->scalarNode('enabled')->defaultTrue()->end()
->scalarNode('enabled')->end()
->end()
;
@ -401,181 +479,4 @@ class Configuration implements ConfigurationInterface
return $node;
}
/**
* Returns the array node used for "_parent".
*/
protected function getParentNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_parent');
$node
->children()
->scalarNode('type')->end()
->scalarNode('property')->defaultValue(null)->end()
->scalarNode('identifier')->defaultValue('id')->end()
->end()
;
return $node;
}
/**
* Returns the array node used for "_all".
*/
protected function getAllNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_all');
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->end()
;
return $node;
}
/**
* Returns the array node used for "_timestamp".
*/
protected function getTimestampNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_timestamp');
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('path')->end()
->scalarNode('format')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->end()
;
return $node;
}
/**
* Returns the array node used for "_ttl".
*/
protected function getTtlNode()
{
$builder = new TreeBuilder();
$node = $builder->root('_ttl');
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('default')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->end()
;
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;
}
}

View file

@ -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;
@ -12,42 +13,20 @@ use InvalidArgumentException;
class FOSElasticaExtension extends Extension
{
/**
* Definition of elastica clients as configured by this 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();
public function load(array $configs, ContainerBuilder $container)
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('config.xml');
if (empty($config['clients']) || empty($config['indexes'])) {
// No Clients or indexes are defined
return;
}
foreach (array('config', 'index', 'persister', 'provider', 'source', 'transformer') as $basename) {
$loader->load(sprintf('%s.xml', $basename));
throw new InvalidArgumentException('You must define at least one client and one index');
}
if (empty($config['default_client'])) {
@ -60,129 +39,109 @@ class FOSElasticaExtension extends Extension
$config['default_index'] = reset($keys);
}
if (isset($config['serializer'])) {
$loader->load('serializer.xml');
$clientIdsByName = $this->loadClients($config['clients'], $container);
$indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']);
$indexRefsByName = array_map(function($id) {
return new Reference($id);
}, $indexIdsByName);
$this->loadSerializer($config['serializer'], $container);
}
$this->loadIndexManager($indexRefsByName, $container);
$this->loadResetter($this->indexConfigs, $container);
$this->loadClients($config['clients'], $container);
$container->setAlias('fos_elastica.client', sprintf('fos_elastica.client.%s', $config['default_client']));
$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->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'));
return new Configuration($config);
}
/**
* 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)
{
$clientIds = array();
foreach ($clients as $name => $clientConfig) {
$clientId = sprintf('fos_elastica.client.%s', $name);
$clientDef = new DefinitionDecorator('fos_elastica.client_prototype');
$clientDef->replaceArgument(0, $clientConfig);
$logger = $clientConfig['connections'][0]['logger'];
if (false !== $logger) {
$clientDef->addMethodCall('setLogger', array(new Reference($logger)));
}
$clientDef->addTag('fos_elastica.client');
$clientDef = new Definition('%fos_elastica.client.class%', array($clientConfig));
$clientDef->addMethodCall('setLogger', array(new Reference('fos_elastica.logger')));
$container->setDefinition($clientId, $clientDef);
$this->clients[$name] = array(
'id' => $clientId,
'reference' => new Reference($clientId),
);
$clientIds[$name] = $clientId;
}
return $clientIds;
}
/**
* 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
*
* @param array $clientIdsByName
* @param $defaultClientName
* @throws \InvalidArgumentException
*
* @return array
*/
private function loadIndexes(array $indexes, ContainerBuilder $container)
protected function loadIndexes(array $indexes, ContainerBuilder $container, array $clientIdsByName, $defaultClientName)
{
$indexableCallbacks = array();
$indexIds = array();
foreach ($indexes as $name => $index) {
if (isset($index['client'])) {
$clientName = $index['client'];
if (!isset($clientIdsByName[$clientName])) {
throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName));
}
} else {
$clientName = $defaultClientName;
}
$clientId = $clientIdsByName[$clientName];
$indexId = sprintf('fos_elastica.index.%s', $name);
$indexName = isset($index['index_name']) ? $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'])) {
$client = $this->getClient($index['client']);
$indexDef->setFactoryService($client);
}
$indexDefArgs = array($indexName);
$indexDef = new Definition('%fos_elastica.index.class%', $indexDefArgs);
$indexDef->setFactoryService($clientId);
$indexDef->setFactoryMethod('getIndex');
$container->setDefinition($indexId, $indexDef);
$reference = new Reference($indexId);
$typePrototypeConfig = isset($index['type_prototype']) ? $index['type_prototype'] : array();
$indexIds[$name] = $indexId;
$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),
'config' => array(
'mappings' => array()
)
);
if ($index['finder']) {
$this->loadIndexFinder($container, $name, $reference);
$this->loadIndexFinder($container, $name, $indexId);
}
$this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name], $indexableCallbacks);
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
$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 +152,116 @@ 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
*/
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']);
$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');
$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];
}
if (isset($type['_source'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_source'] = $type['_source'];
}
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;
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'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['properties'] = $type['mappings'];
$typeName = sprintf('%s/%s', $indexName, $name);
$this->typeFields[$typeName] = $type['mappings'];
}
$this->indexConfigs[$indexName]['types'][$name] = $typeConfig;
if (isset($type['persistence'])) {
$this->loadTypePersistenceIntegration($type['persistence'], $container, new Reference($typeId), $indexName, $name);
$typeConfig['persistence'] = $type['persistence'];
$this->loadTypePersistenceIntegration($type['persistence'], $container, $typeDef, $indexName, $name);
}
if (isset($type['indexable_callback'])) {
$indexableCallbacks[sprintf('%s/%s', $indexName, $name)] = $type['indexable_callback'];
if (isset($type['index_analyzer'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['index_analyzer'] = $type['index_analyzer'];
}
if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$typeSerializerId = sprintf('%s.serializer.callback', $typeId);
$typeSerializerDef = new DefinitionDecorator('fos_elastica.serializer_callback_prototype');
if (isset($type['serializer']['groups'])) {
$typeSerializerDef->addMethodCall('setGroups', array($type['serializer']['groups']));
}
if (isset($type['serializer']['version'])) {
$typeSerializerDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$typeDef->addMethodCall('setSerializer', array(array(new Reference($typeSerializerId), 'serialize')));
$container->setDefinition($typeSerializerId, $typeSerializerDef);
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'];
}
}
}
/**
* 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,141 +274,69 @@ 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(
'identifier' => $typeConfig['identifier'],
'hydrate' => $typeConfig['elastica_to_model_transformer']['hydrate']
));
$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)
{
$arguments = array(
$typeRef,
new Reference($transformerId),
$typeConfig['model'],
);
if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$abstractId = 'fos_elastica.object_serializer_persister';
$callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['reference'], $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;
}
$serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId);
foreach ($arguments as $i => $argument) {
$serviceDef->replaceArgument($i, $argument);
if (isset($typeConfig['model_to_elastica_transformer']['service'])) {
return $typeConfig['model_to_elastica_transformer']['service'];
}
$serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator('fos_elastica.model_to_elastica_transformer');
$serviceDef->replaceArgument(0, array(
'identifier' => $typeConfig['identifier']
));
$container->setDefinition($serviceId, $serviceDef);
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 loadObjectPersister(array $typeConfig, Definition $typeDef, ContainerBuilder $container, $indexName, $typeName, $transformerId)
{
$serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator('fos_elastica.object_persister');
$serviceDef->replaceArgument(0, $typeDef);
$serviceDef->replaceArgument(1, new Reference($transformerId));
$serviceDef->replaceArgument(2, $typeConfig['model']);
$serviceDef->replaceArgument(3, $this->typeFields[sprintf('%s/%s', $indexName, $typeName)]);
$container->setDefinition($serviceId, $serviceDef);
return $serviceId;
}
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,59 +344,37 @@ 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
);
$tagName = null;
$listenerDef->replaceArgument(1, $typeConfig['model']);
$listenerDef->replaceArgument(3, $typeConfig['identifier']);
$listenerDef->replaceArgument(2, $this->getDoctrineEvents($typeConfig));
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.
*/
private function getDoctrineEvents(array $typeConfig)
{
switch ($typeConfig['driver']) {
case 'orm':
$eventsClass = '\Doctrine\ORM\Events';
break;
case 'mongodb':
$eventsClass = '\Doctrine\ODM\MongoDB\Events';
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
}
$events = array();
$eventMapping = array(
'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')),
'insert' => array('postPersist'),
'update' => array('postUpdate'),
'delete' => array('postRemove', 'preRemove')
);
foreach ($eventMapping as $event => $doctrineEvents) {
@ -546,26 +386,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,63 +410,41 @@ class FOSElasticaExtension extends Extension
}
/**
* Loads the index manager.
* Loads the index manager
*
* @param array $indexRefsByName
* @param ContainerBuilder $container
**/
private function loadIndexManager(ContainerBuilder $container)
protected function loadIndexManager(array $indexRefsByName, ContainerBuilder $container)
{
$indexRefs = array_map(function ($index) {
return $index['reference'];
}, $this->indexConfigs);
$managerDef = $container->getDefinition('fos_elastica.index_manager');
$managerDef->replaceArgument(0, $indexRefs);
$managerDef->replaceArgument(0, $indexRefsByName);
$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
* @param array $indexConfigs
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
private function loadDriver(ContainerBuilder $container, $driver)
protected function loadResetter(array $indexConfigs, ContainerBuilder $container)
{
$resetterDef = $container->getDefinition('fos_elastica.resetter');
$resetterDef->replaceArgument(0, $indexConfigs);
}
protected function loadDriver(ContainerBuilder $container, $driver)
{
if (in_array($driver, $this->loadedDrivers)) {
return;
}
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load($driver.'.xml');
$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;
@ -655,21 +461,4 @@ class FOSElasticaExtension extends Extension
$container->setAlias('fos_elastica.manager', sprintf('fos_elastica.manager.%s', $defaultManagerService));
}
/**
* Returns a reference to a client given its configured name.
*
* @param string $clientName
*
* @return Reference
*
* @throws \InvalidArgumentException
*/
private function getClient($clientName)
{
if (!array_key_exists($clientName, $this->clients)) {
throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName));
}
return $this->clients[$clientName]['reference'];
}
}

View file

@ -2,52 +2,48 @@
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\Form\Util\PropertyPath;
/**
* 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
*/
protected $options = array(
'hydrate' => true,
'identifier' => 'id',
'ignore_missing' => false,
'query_builder_method' => 'createQueryBuilder',
'hydrate' => true,
'identifier' => 'id'
);
/**
* Instantiates a new Mapper.
* Instantiates a new Mapper
*
* @param ManagerRegistry $registry
* @param object $registry
* @param string $objectClass
* @param array $options
* @param array $options
*/
public function __construct(ManagerRegistry $registry, $objectClass, array $options = array())
public function __construct($registry, $objectClass, array $options = array())
{
$this->registry = $registry;
$this->objectClass = $objectClass;
@ -66,12 +62,10 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer
/**
* 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)
@ -83,7 +77,7 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer
}
$objects = $this->findByIdentifiers($ids, $this->options['hydrate']);
if (!$this->options['ignore_missing'] && count($objects) < count($elasticaObjects)) {
if (count($objects) < count($elasticaObjects)) {
throw new \RuntimeException('Cannot find corresponding Doctrine objects for all Elastica results.');
};
@ -93,34 +87,32 @@ abstract class AbstractElasticaToModelTransformer extends BaseTransformer
}
}
$identifierProperty = new PropertyPath($this->options['identifier']);
// sort objects in the order of ids
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
usort($objects, $this->getSortingClosure($idPos, $identifier));
usort($objects, function($a, $b) use ($idPos, $identifierProperty)
{
return $idPos[$identifierProperty->getValue($a)] > $idPos[$identifierProperty->getValue($b)];
});
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 +120,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);
}

View file

@ -0,0 +1,158 @@
<?php
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\ObjectManager;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
abstract class AbstractListener implements EventSubscriber
{
/**
* Object persister
*
* @var ObjectPersister
*/
protected $objectPersister;
/**
* Class of the domain model
*
* @var string
*/
protected $objectClass;
/**
* List of subscribed events
*
* @var array
*/
protected $events;
/**
* Name of domain model field used as the ES identifier
*
* @var string
*/
protected $esIdentifierField;
/**
* Callback for determining if an object should be indexed
*
* @var mixed
*/
protected $isIndexableCallback;
/**
* Objects scheduled for removal
*
* @var array
*/
private $scheduledForRemoval = array();
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param string $objectClass
* @param array $events
* @param string $esIdentifierField
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $events, $esIdentifierField = 'id')
{
$this->objectPersister = $objectPersister;
$this->objectClass = $objectClass;
$this->events = $events;
$this->esIdentifierField = $esIdentifierField;
}
/**
* @see Doctrine\Common\EventSubscriber::getSubscribedEvents()
*/
public function getSubscribedEvents()
{
return $this->events;
}
/**
* 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))) {
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;
}
return is_string($this->isIndexableCallback)
? call_user_func(array($object, $this->isIndexableCallback))
: call_user_func($this->isIndexableCallback, $object);
}
/**
* Schedules the object for removal.
*
* This is usually called during the pre-remove event.
*
* @param object $object
* @param ObjectManager $objectManager
*/
protected function scheduleForRemoval($object, ObjectManager $objectManager)
{
$metadata = $objectManager->getClassMetadata($this->objectClass);
$esId = $metadata->getFieldValue($object, $this->esIdentifierField);
$this->scheduledForRemoval[spl_object_hash($object)] = $esId;
}
/**
* Removes the object if it was scheduled for removal.
*
* This is usually called during the post-remove event.
*
* @param object $object
*/
protected function removeIfScheduled($object)
{
$objectHash = spl_object_hash($object);
if (isset($this->scheduledForRemoval[$objectHash])) {
$this->objectPersister->deleteById($this->scheduledForRemoval[$objectHash]);
unset($this->scheduledForRemoval[$objectHash]);
}
}
}

View file

@ -3,65 +3,68 @@
namespace FOS\ElasticaBundle\Doctrine;
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,
'query_builder_method' => 'createQueryBuilder',
), $options));
$this->managerRegistry = $managerRegistry;
$this->sliceFetcher = $sliceFetcher;
}
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
*/
public function populate(\Closure $loggerClosure = null)
{
$queryBuilder = $this->createQueryBuilder();
$nbObjects = $this->countObjects($queryBuilder);
for ($offset = 0; $offset < $nbObjects; $offset += $this->options['batch_size']) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $this->options['batch_size'], $offset);
$this->objectPersister->insertMany($objects);
if ($this->options['clear_object_manager']) {
$this->managerRegistry->getManagerForClass($this->objectClass)->clear();
}
if ($loggerClosure) {
$stepNbObjects = count($objects);
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond));
}
}
}
/**
* 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 +72,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('<error>%s</error>', $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();
}

View file

@ -1,212 +0,0 @@
<?php
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 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
{
/**
* Object persister.
*
* @var ObjectPersisterInterface
*/
protected $objectPersister;
/**
* Configuration for the listener.
*
* @var array
*/
private $config;
/**
* Objects scheduled for insertion.
*
* @var array
*/
public $scheduledForInsertion = array();
/**
* Objects scheduled to be updated or removed.
*
* @var array
*/
public $scheduledForUpdate = array();
/**
* IDs of objects scheduled for removal.
*
* @var array
*/
public $scheduledForDeletion = array();
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* @var IndexableInterface
*/
private $indexable;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param array $config
* @param LoggerInterface $logger
*/
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();
if ($logger && $this->objectPersister instanceof ObjectPersister) {
$this->objectPersister->setLogger($logger);
}
}
/**
* Looks for new objects that should be indexed.
*
* @param LifecycleEventArgs $eventArgs
*/
public function postPersist(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity) && $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)
{
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate[] = $entity;
} else {
// Delete if no longer indexable
$this->scheduleForDeletion($entity);
}
}
}
/**
* 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
*/
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
}
}
/**
* Persist scheduled objects to ElasticSearch
* After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
*/
private function persistScheduled()
{
if (count($this->scheduledForInsertion)) {
$this->objectPersister->insertMany($this->scheduledForInsertion);
$this->scheduledForInsertion = array();
}
if (count($this->scheduledForUpdate)) {
$this->objectPersister->replaceMany($this->scheduledForUpdate);
$this->scheduledForUpdate = array();
}
if (count($this->scheduledForDeletion)) {
$this->objectPersister->deleteManyByIdentifiers($this->scheduledForDeletion);
$this->scheduledForDeletion = array();
}
}
/**
* Iterate through scheduled actions before flushing to emulate 2.x behavior.
* Note that the ElasticSearch index will fall out of sync with the source
* data in the event of a crash during flush.
*
* This method is only called in legacy configurations of the listener.
*
* @deprecated This method should only be called in applications that depend
* on the behaviour that entities are indexed regardless of if a
* flush is successful.
*/
public function preFlush()
{
$this->persistScheduled();
}
/**
* Iterating through scheduled actions *after* flushing ensures that the
* ElasticSearch index will be affected only if the query is successful.
*/
public function postFlush()
{
$this->persistScheduled();
}
/**
* Record the specified identifier to delete. Do not need to entire object.
*
* @param object $object
*/
private function scheduleForDeletion($object)
{
if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
$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
);
}
}

View file

@ -7,24 +7,22 @@ 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)
->createQueryBuilder($this->objectClass)
->field($this->options['identifier'])->in($identifierValues)
->hydrate($hydrate)
->getQuery()

View file

@ -0,0 +1,50 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\MongoDB;
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass && $this->isObjectIndexable($document)) {
$this->objectPersister->insertOne($document);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
if ($this->isObjectIndexable($document)) {
$this->objectPersister->replaceOne($document);
} else {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
$this->removeIfScheduled($document);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->removeIfScheduled($document);
}
}
}

View file

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

View file

@ -1,44 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\MongoDB;
use Doctrine\ODM\MongoDB\Query\Builder;
use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException;
use FOS\ElasticaBundle\Doctrine\SliceFetcherInterface;
/**
* Fetches a slice of objects.
*
* @author Thomas Prelot <tprelot@gmail.com>
*/
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()
;
}
}

View file

@ -8,18 +8,15 @@ 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)
@ -28,25 +25,14 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
return array();
}
$hydrationMode = $hydrate ? Query::HYDRATE_OBJECT : Query::HYDRATE_ARRAY;
$qb = $this->getEntityQueryBuilder();
$qb->andWhere($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values'))
$qb = $this->registry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
->createQueryBuilder('o');
/* @var $qb \Doctrine\ORM\QueryBuilder */
$qb->where($qb->expr()->in('o.'.$this->options['identifier'], ':values'))
->setParameter('values', $identifierValues);
return $qb->getQuery()->setHydrationMode($hydrationMode)->execute();
}
/**
* Retrieves a query builder to be used for querying by identifiers.
*
* @return \Doctrine\ORM\QueryBuilder
*/
protected function getEntityQueryBuilder()
{
$repository = $this->registry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass);
return $repository->{$this->options['query_builder_method']}(static::ENTITY_ALIAS);
}
}

50
Doctrine/ORM/Listener.php Normal file
View file

@ -0,0 +1,50 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\ORM;
use Doctrine\ORM\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
$this->objectPersister->insertOne($entity);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
if ($this->isObjectIndexable($entity)) {
$this->objectPersister->replaceOne($entity);
} else {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
$this->removeIfScheduled($entity);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->removeIfScheduled($entity);
}
}
}

View file

@ -8,45 +8,8 @@ use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException;
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 +32,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,24 +40,6 @@ class Provider extends AbstractProvider
throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
/*
* An orderBy DQL part is required to avoid fetching the same row twice.
* @see http://stackoverflow.com/questions/6314879/does-limit-offset-length-require-order-by-for-pagination
* @see http://www.postgresql.org/docs/current/static/queries-limit.html
* @see http://www.sqlite.org/lang_select.html#orderby
*/
$orderBy = $queryBuilder->getDQLPart('orderBy');
if (empty($orderBy)) {
$rootAliases = $queryBuilder->getRootAliases();
$identifierFieldNames = $this->managerRegistry
->getManagerForClass($this->objectClass)
->getClassMetadata($this->objectClass)
->getIdentifierFieldNames();
foreach ($identifierFieldNames as $fieldName) {
$queryBuilder->addOrderBy($rootAliases[0].'.'.$fieldName);
}
}
return $queryBuilder
->setFirstResult($offset)
->setMaxResults($limit)
@ -105,14 +48,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']}('a');
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\ORM;
use Doctrine\ORM\QueryBuilder;
use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException;
use FOS\ElasticaBundle\Doctrine\SliceFetcherInterface;
/**
* Fetches a slice of objects.
*
* @author Thomas Prelot <tprelot@gmail.com>
*/
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()
;
}
}

View file

@ -25,19 +25,20 @@ class RepositoryManager extends BaseManager
}
/**
* Return repository for entity.
* Return repository for entity
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
* returns a basic respository.
*/
public function getRepository($entityName)
{
$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);
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine;
/**
* Fetches a slice of objects.
*
* @author Thomas Prelot <tprelot@gmail.com>
*/
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);
}

View file

@ -1,12 +0,0 @@
<?php
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\Elastica\Index;
/**
* @deprecated Use \FOS\ElasticaBundle\Elastica\TransformingIndex
*/
class DynamicIndex extends Index
{
}

View file

@ -1,104 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Client as BaseClient;
use Elastica\Request;
use FOS\ElasticaBundle\Logger\ElasticaLogger;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Extends the default Elastica client to provide logging for errors that occur
* during communication with ElasticSearch.
*
* @author Gordon Franke <info@nevalon.de>
*/
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);
}
}

View file

@ -1,59 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Index as BaseIndex;
/**
* Overridden Elastica Index class that provides dynamic index name changes.
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
*/
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;
}
}

View file

@ -1,38 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
class 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;
}
}

View file

@ -1,70 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
/**
* Index Populate Event.
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
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;
}
}

View file

@ -1,70 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
/**
* Index ResetEvent.
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
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;
}
}

View file

@ -1,78 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
class TransformEvent extends Event
{
const POST_TRANSFORM = 'fos_elastica.post_transform';
/**
* @var mixed
*/
private $document;
/**
* @var array
*/
private $fields;
/**
* @var mixed
*/
private $object;
/**
* @param mixed $document
* @param array $fields
* @param mixed $object
*/
public function __construct($document, array $fields, $object)
{
$this->document = $document;
$this->fields = $fields;
$this->object = $object;
}
/**
* @return mixed
*/
public function getDocument()
{
return $this->document;
}
/**
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* @return mixed
*/
public function getObject()
{
return $this->object;
}
/**
* @param mixed $document
*/
public function setDocument($document)
{
$this->document = $document;
}
}

View file

@ -1,51 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Type Populate Event.
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
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;
}
}

View file

@ -1,49 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Event;
use Symfony\Component\EventDispatcher\Event;
/**
* Type ResetEvent.
*
* @author Oleg Andreyev <oleg.andreyev@intexsys.lv>
*/
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;
}
}

View file

@ -1,11 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Exception;
class AliasIsIndexException extends \Exception
{
public function __construct($indexName)
{
parent::__construct(sprintf('Expected %s to be an alias but it is an index.', $indexName));
}
}

View file

@ -2,28 +2,21 @@
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\DependencyInjection\Compiler\ConfigSourcePass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\IndexPass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Bundle.
*/
class FOSElasticaBundle extends Bundle
{
/**
* {@inheritdoc}
* @see Symfony\Component\HttpKernel\Bundle\Bundle::build()
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ConfigSourcePass());
$container->addCompilerPass(new IndexPass());
$container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TransformerPass());
}

View file

@ -5,13 +5,11 @@ namespace FOS\ElasticaBundle\Finder;
interface FinderInterface
{
/**
* 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 array $options
* 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
* @return array results
*/
public function find($query, $limit = null, $options = array());
function find($query, $limit = null);
}

View file

@ -4,27 +4,23 @@ namespace FOS\ElasticaBundle\Finder;
use FOS\ElasticaBundle\Paginator\PaginatorAdapterInterface;
use Pagerfanta\Pagerfanta;
use Elastica\Query;
use Elastica_Query;
interface PaginatedFinderInterface extends FinderInterface
{
/**
* Searches for query results and returns them wrapped in a paginator.
*
* @param mixed $query Can be a string, an array or an \Elastica\Query object
* @param array $options
* Searches for query results and returns them wrapped in a paginator
*
* @param mixed $query Can be a string, an array or an Elastica_Query object
* @return Pagerfanta paginated results
*/
public function findPaginated($query, $options = array());
function findPaginated($query);
/**
* Creates a paginator adapter for this query.
* Creates a paginator adapter for this query
*
* @param mixed $query
* @param array $options
*
* @return PaginatorAdapterInterface
*/
public function createPaginatorAdapter($query, $options = array());
function createPaginatorAdapter($query);
}

View file

@ -2,47 +2,46 @@
namespace FOS\ElasticaBundle\Finder;
use Elastica\Document;
use FOS\ElasticaBundle\Finder\PaginatedFinderInterface;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use FOS\ElasticaBundle\Paginator\TransformedPaginatorAdapter;
use FOS\ElasticaBundle\Paginator\FantaPaginatorAdapter;
use Pagerfanta\Pagerfanta;
use Elastica\SearchableInterface;
use Elastica\Query;
use Elastica_Document;
use Elastica_Searchable;
use Elastica_Query;
/**
* Finds elastica documents and map them to persisted objects.
* Finds elastica documents and map them to persisted objects
*/
class TransformedFinder implements PaginatedFinderInterface
{
protected $searchable;
protected $transformer;
public function __construct(SearchableInterface $searchable, ElasticaToModelTransformerInterface $transformer)
public function __construct(Elastica_Searchable $searchable, ElasticaToModelTransformerInterface $transformer)
{
$this->searchable = $searchable;
$this->transformer = $transformer;
}
/**
* Search for a query string.
* Search for a query string
*
* @param string $query
* @param string $query
* @param integer $limit
* @param array $options
*
* @return array of model objects
**/
public function find($query, $limit = null, $options = array())
public function find($query, $limit = null)
{
$results = $this->search($query, $limit, $options);
$results = $this->search($query, $limit);
return $this->transformer->transform($results);
}
public function findHybrid($query, $limit = null, $options = array())
public function findHybrid($query, $limit = null)
{
$results = $this->search($query, $limit, $options);
$results = $this->search($query, $limit);
return $this->transformer->hybridTransform($results);
}
@ -51,14 +50,13 @@ 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())
{
$doc = new Document($id);
$doc = new Elastica_Document($id);
$results = $this->searchable->moreLikeThis($doc, $params, $query)->getResults();
return $this->transformer->transform($results);
@ -67,33 +65,29 @@ class TransformedFinder implements PaginatedFinderInterface
/**
* @param $query
* @param null|int $limit
* @param array $options
*
* @return array
*/
protected function search($query, $limit = null, $options = array())
protected function search($query, $limit = null)
{
$queryObject = Query::create($query);
$queryObject = Elastica_Query::create($query);
if (null !== $limit) {
$queryObject->setSize($limit);
$queryObject->setLimit($limit);
}
$results = $this->searchable->search($queryObject, $options)->getResults();
$results = $this->searchable->search($queryObject)->getResults();
return $results;
}
/**
* Gets a paginator wrapping the result of a search.
* Gets a paginator wrapping the result of a search
*
* @param string $query
* @param array $options
*
* @return Pagerfanta
*/
public function findPaginated($query, $options = array())
public function findPaginated($query)
{
$queryObject = Query::create($query);
$paginatorAdapter = $this->createPaginatorAdapter($queryObject, $options);
$queryObject = Elastica_Query::create($query);
$paginatorAdapter = $this->createPaginatorAdapter($queryObject);
return new Pagerfanta(new FantaPaginatorAdapter($paginatorAdapter));
}
@ -101,10 +95,9 @@ class TransformedFinder implements PaginatedFinderInterface
/**
* {@inheritdoc}
*/
public function createPaginatorAdapter($query, $options = array())
public function createPaginatorAdapter($query)
{
$query = Query::create($query);
return new TransformedPaginatorAdapter($this->searchable, $query, $options, $this->transformer);
$query = Elastica_Query::create($query);
return new TransformedPaginatorAdapter($this->searchable, $query, $this->transformer);
}
}

View file

@ -2,14 +2,14 @@
namespace FOS\ElasticaBundle;
use Elastica\Result;
use Elastica_Result;
class HybridResult
{
protected $result;
protected $transformed;
public function __construct(Result $result, $transformed = null)
public function __construct(Elastica_Result $result, $transformed = null)
{
$this->result = $result;
$this->transformed = $transformed;
@ -24,4 +24,4 @@ class HybridResult
{
return $this->result;
}
}
}

View file

@ -1,197 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\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);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Index;
use FOS\ElasticaBundle\Elastica\Index;
class IndexManager
{
/**
* @var Index
*/
private $defaultIndex;
/**
* @var array
*/
private $indexes;
/**
* @param array $indexes
* @param Index $defaultIndex
*/
public function __construct(array $indexes, Index $defaultIndex)
{
$this->defaultIndex = $defaultIndex;
$this->indexes = $indexes;
}
/**
* Gets all registered indexes.
*
* @return array
*/
public function getAllIndexes()
{
return $this->indexes;
}
/**
* Gets an index by its name.
*
* @param string $name Index to return, or the default index if null
*
* @return Index
*
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function getIndex($name = null)
{
if (null === $name) {
return $this->defaultIndex;
}
if (!isset($this->indexes[$name])) {
throw new \InvalidArgumentException(sprintf('The index "%s" does not exist', $name));
}
return $this->indexes[$name];
}
/**
* Gets the default index.
*
* @return Index
*/
public function getDefaultIndex()
{
return $this->defaultIndex;
}
}

View file

@ -1,134 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\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;
}
}
}
}

View file

@ -1,158 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Index;
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.
*/
class Resetter
{
/**
* @var AliasProcessor
*/
private $aliasProcessor;
/***
* @var ConfigManager
*/
private $configManager;
/**
* @var EventDispatcherInterface
*/
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;
}
/**
* Deletes and recreates all indexes.
*
* @param bool $populating
* @param bool $force
*/
public function resetAllIndexes($populating = false, $force = false)
{
foreach ($this->configManager->getIndexNames() as $name) {
$this->resetIndex($name, $populating, $force);
}
}
/**
* Deletes and recreates the named index. If populating, creates a new index
* with a randomised name for an alias to be set after population.
*
* @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)
{
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
$index = $this->indexManager->getIndex($indexName);
$event = new IndexResetEvent($indexName, $populating, $force);
$this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
if ($indexConfig->isUseAlias()) {
$this->aliasProcessor->setRootName($indexConfig, $index);
}
$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);
}
/**
* 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);
$event = new TypeResetEvent($indexName, $typeName);
$this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
try {
$type->delete();
} catch (ResponseException $e) {
if (strpos($e->getMessage(), 'TypeMissingException') === false) {
throw $e;
}
}
$mapping = new Mapping();
foreach ($this->mappingBuilder->buildTypeMapping($typeConfig) as $name => $field) {
$mapping->setParam($name, $field);
}
$type->setMapping($mapping);
$this->dispatcher->dispatch(TypeResetEvent::POST_TYPE_RESET, $event);
}
/**
* A command run when a population has finished.
*
* @param string $indexName
*/
public function postPopulate($indexName)
{
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
if ($indexConfig->isUseAlias()) {
$index = $this->indexManager->getIndex($indexName);
$this->aliasProcessor->switchIndexAlias($indexConfig, $index);
}
}
}

View file

@ -2,11 +2,60 @@
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\Index\IndexManager as BaseIndexManager;
/**
* @deprecated Use \FOS\ElasticaBundle\Index\IndexManager
*/
class IndexManager extends BaseIndexManager
class IndexManager
{
protected $indexesByName;
protected $defaultIndexName;
/**
* Constructor.
*
* @param array $indexesByName
* @param \Elastica_Index $defaultIndex
*/
public function __construct(array $indexesByName, \Elastica_Index $defaultIndex)
{
$this->indexesByName = $indexesByName;
$this->defaultIndexName = $defaultIndex->getName();
}
/**
* Gets all registered indexes
*
* @return array
*/
public function getAllIndexes()
{
return $this->indexesByName;
}
/**
* Gets an index by its name
*
* @param string $name Index to return, or the default index if null
* @return \Elastica_Index
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function getIndex($name = null)
{
if (null === $name) {
$name = $this->defaultIndexName;
}
if (!isset($this->indexesByName[$name])) {
throw new \InvalidArgumentException(sprintf('The index "%s" does not exist', $name));
}
return $this->indexesByName[$name];
}
/**
* Gets the default index
*
* @return \Elastica_Index
*/
public function getDefaultIndex()
{
return $this->getIndex($this->defaultIndexName);
}
}

18
LICENSE Normal file
View file

@ -0,0 +1,18 @@
Copyright (c) 2012 Exercise.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -2,7 +2,7 @@
namespace FOS\ElasticaBundle\Logger;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Log\LoggerInterface;
/**
* Logger for the Elastica.
@ -12,55 +12,41 @@ use Psr\Log\LoggerInterface;
*
* @author Gordon Franke <info@nevalon.de>
*/
class ElasticaLogger implements LoggerInterface
class ElasticaLogger
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var array
*/
protected $queries = array();
/**
* @var boolean
*/
protected $queries;
protected $debug;
/**
* Constructor.
*
* @param LoggerInterface|null $logger The Symfony logger
* @param boolean $debug
* @param bool $debug
*/
public function __construct(LoggerInterface $logger = null, $debug = false)
{
$this->logger = $logger;
$this->queries = array();
$this->debug = $debug;
}
/**
* Logs a query.
*
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data Arguments
* @param float $time Execution time
* @param array $connection Host, port, transport, and headers of the query
* @param array $query Arguments
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data arguments
* @param float $time execution time
*/
public function logQuery($path, $method, $data, $time, $connection = array(), $query = array())
public function logQuery($path, $method, $data, $time)
{
if ($this->debug) {
$this->queries[] = array(
'path' => $path,
'method' => $method,
'data' => $data,
'executionMS' => $time,
'connection' => $connection,
'queryString' => $query,
'executionMS' => $time
);
}
@ -89,76 +75,4 @@ class ElasticaLogger implements LoggerInterface
{
return $this->queries;
}
/**
* {@inheritdoc}
*/
public function emergency($message, array $context = array())
{
return $this->logger->emergency($message, $context);
}
/**
* {@inheritdoc}
*/
public function alert($message, array $context = array())
{
return $this->logger->alert($message, $context);
}
/**
* {@inheritdoc}
*/
public function critical($message, array $context = array())
{
return $this->logger->critical($message, $context);
}
/**
* {@inheritdoc}
*/
public function error($message, array $context = array())
{
return $this->logger->error($message, $context);
}
/**
* {@inheritdoc}
*/
public function warning($message, array $context = array())
{
return $this->logger->warning($message, $context);
}
/**
* {@inheritdoc}
*/
public function notice($message, array $context = array())
{
return $this->logger->notice($message, $context);
}
/**
* {@inheritdoc}
*/
public function info($message, array $context = array())
{
return $this->logger->info($message, $context);
}
/**
* {@inheritdoc}
*/
public function debug($message, array $context = array())
{
return $this->logger->debug($message, $context);
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
return $this->logger->log($level, $message, $context);
}
}

View file

@ -5,7 +5,6 @@ namespace FOS\ElasticaBundle\Manager;
use Doctrine\Common\Annotations\Reader;
use FOS\ElasticaBundle\Finder\FinderInterface;
use RuntimeException;
/**
* @author Richard Miller <info@limethinking.co.uk>
*
@ -25,16 +24,16 @@ 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.
* returns a basic respository.
*/
public function getRepository($entityName)
{
@ -59,26 +58,23 @@ 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))) {
$repositoryName = $this->getRepositoryName($entityName);
if (!class_exists($repositoryName)) {
throw new RuntimeException(sprintf('%s repository for %s does not exist', $repositoryName, $entityName));
}
return new $repositoryName($this->entities[$entityName]['finder']);
}
}

View file

@ -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.
@ -31,4 +32,5 @@ interface RepositoryManagerInterface
* @param string $entityName
*/
public function getRepository($entityName);
}

View file

@ -3,13 +3,14 @@
namespace FOS\ElasticaBundle\Paginator;
use Pagerfanta\Adapter\AdapterInterface;
use FOS\ElasticaBundle\Paginator\PaginatorAdapterInterface;
class FantaPaginatorAdapter implements AdapterInterface
{
private $adapter;
/**
* @param \FOS\ElasticaBundle\Paginator\PaginatorAdapterInterface $adapter
* @param PaginatorAdapterInterface $adapter
*/
public function __construct(PaginatorAdapterInterface $adapter)
{
@ -20,6 +21,8 @@ class FantaPaginatorAdapter implements AdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
public function getNbResults()
{
@ -27,34 +30,14 @@ class FantaPaginatorAdapter implements AdapterInterface
}
/**
* 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.
* Returns an slice of the results.
*
* @param integer $offset The offset.
* @param integer $length The length.
*
* @return array|\Traversable The slice.
*
* @api
*/
public function getSlice($offset, $length)
{

View file

@ -2,14 +2,18 @@
namespace FOS\ElasticaBundle\Paginator;
use FOS\ElasticaBundle\Paginator\PartialResultsInterface;
interface PaginatorAdapterInterface
{
/**
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
public function getTotalHits();
function getTotalHits();
/**
* Returns an slice of the results.
@ -18,20 +22,8 @@ interface PaginatorAdapterInterface
* @param integer $length The length.
*
* @return PartialResultsInterface
*/
public function getResults($offset, $length);
/**
* Returns Facets.
*
* @return mixed
* @api
*/
public function getFacets();
/**
* Returns Aggregations.
*
* @return mixed
*/
public function getAggregations();
function getResults($offset, $length);
}

View file

@ -8,27 +8,24 @@ interface PartialResultsInterface
* Returns the paginated results.
*
* @return array
*
* @api
*/
public function toArray();
function toArray();
/**
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
public function getTotalHits();
function getTotalHits();
/**
* Returns the facets.
* Returns the facets
*
* @return array
*/
public function getFacets();
/**
* Returns the aggregations.
*
* @return array
*/
public function getAggregations();
}
function getFacets();
}

View file

@ -2,96 +2,54 @@
namespace FOS\ElasticaBundle\Paginator;
use Elastica\SearchableInterface;
use Elastica\Query;
use Elastica\ResultSet;
use InvalidArgumentException;
use Elastica_Searchable;
use Elastica_Query;
use Elastica_ResultSet;
use FOS\ElasticaBundle\Paginator\PaginatorAdapterInterface;
use FOS\ElasticaBundle\Paginator\RawPartialResults;
use FOS\ElasticaBundle\Paginator\PartialResultsInterface;
/**
* Allows pagination of Elastica\Query. Does not map results.
* Allows pagination of Elastica_Query. Does not map results
*/
class RawPaginatorAdapter implements PaginatorAdapterInterface
{
/**
* @var SearchableInterface the object to search in
* @var Elastica_Searchable the object to search in
*/
private $searchable;
private $searchable = null;
/**
* @var Query the query to search
* @var Elastica_Query the query to search
*/
private $query;
/**
* @var array search options
*/
private $options;
/**
* @var integer the number of hits
*/
private $totalHits;
/**
* @var array for the facets
*/
private $facets;
/**
* @var array for the aggregations
*/
private $aggregations;
private $query = null;
/**
* @see PaginatorAdapterInterface::__construct
*
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
* @param Elastica_Searchable $searchable the object to search in
* @param Elastica_Query $query the query to search
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options = array())
public function __construct(Elastica_Searchable $searchable, Elastica_Query $query)
{
$this->searchable = $searchable;
$this->query = $query;
$this->options = $options;
}
/**
* Returns the paginated results.
*
* @param integer $offset
* @param integer $itemCountPerPage
*
* @throws \InvalidArgumentException
*
* @return ResultSet
* @param $offset
* @param $itemCountPerPage
* @return Elastica_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);
$query->setLimit($itemCountPerPage);
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
$this->aggregations = $resultSet->getAggregations();
return $resultSet;
return $this->searchable->search($query);
}
/**
@ -99,7 +57,6 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*
* @param int $offset
* @param int $itemCountPerPage
*
* @return PartialResultsInterface
*/
public function getResults($offset, $itemCountPerPage)
@ -110,60 +67,10 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the number of results.
*
* If genuineTotal is provided as true, total hits is returned from the
* hits.total value from the search results instead of just returning
* the requested size.
*
* @param boolean $genuineTotal
*
* @return integer The number of results.
*/
public function getTotalHits($genuineTotal = false)
public function getTotalHits()
{
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;
return $this->searchable->search($this->query)->getTotalHits();
}
}

View file

@ -2,20 +2,21 @@
namespace FOS\ElasticaBundle\Paginator;
use Elastica\ResultSet;
use Elastica\Result;
use FOS\ElasticaBundle\Paginator\PartialResultsInterface;
use Elastica_ResultSet;
use Elastica_Result;
/**
* Raw partial results transforms to a simple array.
* Raw partial results transforms to a simple array
*/
class RawPartialResults implements PartialResultsInterface
{
protected $resultSet;
/**
* @param ResultSet $resultSet
* @param \Elastica_ResultSet $resultSet
*/
public function __construct(ResultSet $resultSet)
public function __construct(Elastica_ResultSet $resultSet)
{
$this->resultSet = $resultSet;
}
@ -25,7 +26,7 @@ class RawPartialResults implements PartialResultsInterface
*/
public function toArray()
{
return array_map(function (Result $result) {
return array_map(function(Elastica_Result $result) {
return $result->getSource();
}, $this->resultSet->getResults());
}
@ -47,18 +48,6 @@ class RawPartialResults implements PartialResultsInterface
return $this->resultSet->getFacets();
}
return;
return null;
}
/**
* {@inheritDoc}
*/
public function getAggregations()
{
if ($this->resultSet->hasAggregations()) {
return $this->resultSet->getAggregations();
}
return;
}
}
}

View file

@ -3,25 +3,25 @@
namespace FOS\ElasticaBundle\Paginator;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Elastica\SearchableInterface;
use Elastica\Query;
use FOS\ElasticaBundle\Paginator\TransformedPartialResults;
use Elastica_Searchable;
use Elastica_Query;
/**
* Allows pagination of \Elastica\Query.
* Allows pagination of Elastica_Query
*/
class TransformedPaginatorAdapter extends RawPaginatorAdapter
{
private $transformer;
/**
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
* @param Elastica_Searchable $searchable the object to search in
* @param Elastica_Query $query the query to search
* @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options = array(), ElasticaToModelTransformerInterface $transformer)
public function __construct(Elastica_Searchable $searchable, Elastica_Query $query, ElasticaToModelTransformerInterface $transformer)
{
parent::__construct($searchable, $query, $options);
parent::__construct($searchable, $query);
$this->transformer = $transformer;
}

View file

@ -3,20 +3,21 @@
namespace FOS\ElasticaBundle\Paginator;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Elastica\ResultSet;
use FOS\ElasticaBundle\Paginator\RawPartialResults;
use Elastica_ResultSet;
/**
* Partial transformed result set.
* Partial transformed result set
*/
class TransformedPartialResults extends RawPartialResults
{
protected $transformer;
/**
* @param ResultSet $resultSet
* @param \Elastica_ResultSet $resultSet
* @param \FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface $transformer
*/
public function __construct(ResultSet $resultSet, ElasticaToModelTransformerInterface $transformer)
public function __construct(Elastica_ResultSet $resultSet, ElasticaToModelTransformerInterface $transformer)
{
parent::__construct($resultSet);
@ -30,4 +31,4 @@ class TransformedPartialResults extends RawPartialResults
{
return $this->transformer->transform($this->resultSet->getResults());
}
}
}

View file

@ -2,15 +2,13 @@
namespace FOS\ElasticaBundle\Persister;
use Psr\Log\LoggerInterface;
use Elastica\Exception\BulkException;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
use Elastica\Type;
use Elastica\Document;
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 <thibault.duplessis@gmail.com>
*/
@ -20,9 +18,8 @@ class ObjectPersister implements ObjectPersisterInterface
protected $transformer;
protected $objectClass;
protected $fields;
protected $logger;
public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, array $fields)
public function __construct(Elastica_Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, array $fields)
{
$this->type = $type;
$this->transformer = $transformer;
@ -30,161 +27,75 @@ 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.
*
* @param BulkException $e
*
* @throws BulkException
*/
private function log(BulkException $e)
{
if (! $this->logger) {
throw $e;
}
$this->logger->error($e);
}
/**
* 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);
$this->type->deleteById($document->getId());
$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);
$this->type->deleteById($document->getId());
}
/**
* 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));
$this->type->deleteById($id);
}
/**
* Bulk insert an array of objects in the type for the given method.
* Inserts an array of objects in the type
*
* @param array $objects array of domain model objects
* @param string Method to call
*/
**/
public function insertMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
try {
$this->type->addDocuments($documents);
} catch (BulkException $e) {
$this->log($e);
}
$this->type->addDocuments($documents);
}
/**
* Bulk update an array of objects in the type. Create document if it does not already exist.
*
* @param array $objects array of domain model objects
*/
public function replaceMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$document = $this->transformToElasticaDocument($object);
$document->setDocAsUpsert(true);
$documents[] = $document;
}
try {
$this->type->updateDocuments($documents);
} catch (BulkException $e) {
$this->log($e);
}
}
/**
* Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
public function deleteMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
try {
$this->type->deleteDocuments($documents);
} catch (BulkException $e) {
$this->log($e);
}
}
/**
* Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/
public function deleteManyByIdentifiers(array $identifiers)
{
try {
$this->type->getIndex()->getClient()->deleteIds($identifiers, $this->type->getIndex(), $this->type);
} catch (BulkException $e) {
$this->log($e);
}
}
/**
* Transforms an object to an elastica document.
* Transforms an object to an elastica document
*
* @param object $object
*
* @return Document the elastica document
* @return Elastica_Document the elastica document
*/
public function transformToElasticaDocument($object)
{

View file

@ -4,75 +4,47 @@ 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 <thibault.duplessis@gmail.com>
*/
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
*/
public function deleteById($id);
*
* @return null
**/
function deleteById($id);
/**
* Bulk inserts an array of objects in the type.
* Inserts an array of objects in the type
*
* @param array $objects array of domain model objects
*/
public function insertMany(array $objects);
/**
* Bulk updates an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
public function replaceMany(array $objects);
/**
* Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
public function deleteMany(array $objects);
/**
* Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/
public function deleteManyByIdentifiers(array $identifiers);
**/
function insertMany(array $objects);
}

View file

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Persister;
use Elastica\Document;
use Elastica\Type;
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.
*
* @author Lea Haensenberber <lea.haensenberger@gmail.com>
*/
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;
}
/**
* Transforms an object to an elastica document
* with just the identifier set.
*
* @param object $object
*
* @return Document the elastica document
*/
public function transformToElasticaDocument($object)
{
$document = $this->transformer->transform($object, array());
$data = call_user_func($this->serializer, $object);
$document->setData($data);
return $document;
}
}

View file

@ -3,75 +3,74 @@
namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Form\Util\PropertyPath;
/**
* Maps Elastica documents with Propel objects.
*
* This mapper assumes an exact match between Elastica document IDs and Propel
* entity IDs.
* Maps Elastica documents with Propel objects
* This mapper assumes an exact match between
* elastica documents ids and propel object ids
*
* @author William Durand <william.durand1@gmail.com>
*/
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
{
/**
* Propel model class to map to Elastica documents.
* Class of the model to map to the elastica documents
*
* @var string
*/
protected $objectClass = null;
/**
* Transformer options.
* Optional parameters
*
* @var array
*/
protected $options = array(
'hydrate' => true,
'identifier' => 'id',
'identifier' => 'id'
);
/**
* Constructor.
* Instantiates a new Mapper
*
* @param string $objectClass
* @param array $options
* @param array $options
*/
public function __construct($objectClass, array $options = array())
{
$this->objectClass = $objectClass;
$this->options = array_merge($this->options, $options);
$this->options = array_merge($this->options, $options);
}
/**
* Transforms an array of Elastica document into an array of Propel entities
* fetched from the database.
* Transforms an array of elastica objects into an array of
* model objects fetched from the propel repository
*
* @param array $elasticaObjects
*
* @return array|\ArrayObject
* @param \Elastica_Document[] $elasticaObjects array of elastica objects
* @return array
*/
public function transform(array $elasticaObjects)
{
$ids = array();
foreach ($elasticaObjects as $elasticaObject) {
$ids[] = $elasticaObject->getId();
}
$ids = array_map(function(\Elastica_Document $elasticaObject) {
return $elasticaObject->getId();
}, $elasticaObjects);
$objects = $this->findByIdentifiers($ids, $this->options['hydrate']);
// Sort objects in the order of their IDs
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
$sortCallback = $this->getSortingClosure($idPos, $identifier);
$identifierProperty = new PropertyPath($this->options['identifier']);
// sort objects in the order of ids
$idPos = array_flip($ids);
if (is_object($objects)) {
$objects->uasort($sortCallback);
$objects->uasort(function($a, $b) use ($idPos, $identifierProperty) {
return $idPos[$identifierProperty->getValue($a)] > $idPos[$identifierProperty->getValue($b)];
});
} else {
usort($objects, $sortCallback);
usort($objects, function($a, $b) use ($idPos, $identifierProperty) {
return $idPos[$identifierProperty->getValue($a)] > $idPos[$identifierProperty->getValue($b)];
});
}
return $objects;
@ -85,7 +84,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]);
}
@ -109,15 +108,11 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
}
/**
* Fetch Propel entities for the given identifier values.
* Fetch objects for theses identifier values
*
* If $hydrate is false, the returned array elements will be arrays.
* Otherwise, the results will be hydrated to instances of the model class.
*
* @param array $identifierValues Identifier values
* @param boolean $hydrate Whether or not to hydrate the results
*
* @return array
* @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)
{
@ -127,7 +122,7 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
$query = $this->createQuery($this->objectClass, $this->options['identifier'], $identifierValues);
if (! $hydrate) {
if (!$hydrate) {
return $query->toArray();
}
@ -137,10 +132,9 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
/**
* Create a query to use in the findByIdentifiers() method.
*
* @param string $class Propel model class
* @param string $identifierField Identifier field name (e.g. "id")
* @param array $identifierValues Identifier values
*
* @param string $class the model class
* @param string $identifierField like 'id'
* @param array $identifierValues ids values
* @return \ModelCriteria
*/
protected function createQuery($class, $identifierField, array $identifierValues)
@ -148,13 +142,13 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
$queryClass = $class.'Query';
$filterMethod = 'filterBy'.$this->camelize($identifierField);
return $queryClass::create()->$filterMethod($identifierValues);
return $queryClass::create()
->$filterMethod($identifierValues)
;
}
/**
* @see https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Util/Inflector.php
*
* @param string $str
*/
private function camelize($str)
{

View file

@ -5,69 +5,39 @@ namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\Provider\AbstractProvider;
/**
* Propel provider.
* Propel provider
*
* @author William Durand <william.durand1@gmail.com>
*/
class Provider extends AbstractProvider
{
/**
* {@inheritDoc}
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
*/
public function doPopulate($options, \Closure $loggerClosure = null)
public function populate(\Closure $loggerClosure = null)
{
$queryClass = $this->objectClass.'Query';
$queryClass = $this->objectClass . 'Query';
$nbObjects = $queryClass::create()->count();
$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 = 0; $offset < $nbObjects; $offset += $this->options['batch_size']) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
usleep($options['sleep']);
$objects = $queryClass::create()
->limit($this->options['batch_size'])
->offset($offset)
->find();
$this->objectPersister->insertMany($objects->getArrayCopy());
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', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond));
}
}
}
/**
* {@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)
{
}
}

View file

@ -3,193 +3,28 @@
namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use FOS\ElasticaBundle\Provider\ProviderInterface;
/**
* AbstractProvider.
*/
abstract class AbstractProvider implements ProviderInterface
{
/**
* @var array
*/
protected $baseOptions;
/**
* @var string
*/
protected $objectClass;
/**
* @var ObjectPersisterInterface
*/
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;
}
/**
* 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
*
* @return string
*/
protected function getMemoryUsage()
{
$memory = round(memory_get_usage() / (1024 * 1024)); // to get usage in Mo
$memoryMax = round(memory_get_peak_usage() / (1024 * 1024)); // to get max usage in Mo
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));
), $options);
}
}

View file

@ -1,240 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\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
));
}
}

View file

@ -1,26 +0,0 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\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);
}

View file

@ -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 <thibault.duplessis@gmail.com>
*/
@ -12,15 +12,8 @@ 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);
}

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Provider;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -57,10 +59,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 +83,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)

660
README.md
View file

@ -1,35 +1,651 @@
FOSElasticaBundle
=================
[Elastica](https://github.com/ruflin/Elastica) integration in Symfony2
This bundle provides integration with [ElasticSearch](http://www.elasticsearch.org) and [Elastica](https://github.com/ruflin/Elastica) with
Symfony2. Features include:
### Installation
- Integrates the Elastica library into a Symfony2 environment
- Automatically generate mappings using a serializer
- Listeners for Doctrine events for automatic indexing
#### Bundle and Dependencies
> **Note** Propel support is limited and contributions fixing issues are welcome!
For Symfony 2.0.x projects, you must use a 1.x release of this bundle. Please
check the bundle
[tags](https://github.com/FriendsOfSymfony/FOSElasticaBundle/tags) or the
[Packagist](https://packagist.org/packages/friendsofsymfony/elastica-bundle)
page for information on Symfony and Elastica compatibility.
[![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)
Add FOSElasticaBundle to your application's `composer.json` file:
Documentation
-------------
```json
{
"require": {
"friendsofsymfony/elastica-bundle": "~2.0"
}
}
```
Documentation for FOSElasticaBundle is in `Resources/doc/index.md`
Install the bundle and its dependencies with the following command:
[Read the documentation for 3.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
```bash
$ php composer.phar update friendsofsymfony/elastica-bundle
```
[Read the documentation for 3.0.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/3.0.x/Resources/doc/index.md)
You may rely on Composer to fetch the appropriate version of Elastica. Lastly,
enable the bundle in your application kernel:
Installation
------------
```php
// app/AppKernel.php
Installation instructions can be found in the [documentation](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/setup.md)
public function registerBundles()
{
$bundles = array(
// ...
new FOS\ElasticaBundle\FOSElasticaBundle(),
);
}
```
License
-------
#### Elasticsearch
This bundle is under the MIT license. See the complete license in the bundle:
Instructions for installing and deploying Elasticsearch may be found
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
Resources/meta/LICENSE
### Basic configuration
#### Declare a client
Elasticsearch client is comparable to a database connection.
Most of the time, you will need only one.
#app/config/config.yml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
#### Declare an index
Elasticsearch index is comparable to Doctrine entity manager.
Most of the time, you will need only one.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
Here we created a "website" index, that uses our "default" client.
Our index is now available as a service: `fos_elastica.index.website`. It is an instance of `Elastica_Index`.
If you need to have different index name from the service name, for example,
in order to have different indexes for different environments then you can
use the ```index_name``` key to change the index name. The service name will
remain the same across the environments:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
index_name: website_qa
The service id will be `fos_elastica.index.website` but the underlying index name is website_qa.
#### Declare a type
Elasticsearch type is comparable to Doctrine entity repository.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
username: { boost: 5 }
firstName: { boost: 3 }
lastName: { boost: 3 }
aboutMe: ~
Our type is now available as a service: `fos_elastica.index.website.user`. It is an instance of `Elastica_Type`.
### Declaring parent field
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
comment:
mappings:
post: {_parent: { type: "post", identifier: "id" } }
date: { boost: 5 }
content: ~
### Declaring `nested` or `object`
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
post:
mappings:
date: { boost: 5 }
title: { boost: 3 }
content: ~
comments:
type: "nested"
properties:
date: { boost: 5 }
content: ~
### Populate the types
php app/console fos:elastica:populate
This command deletes and creates the declared indexes and types.
It applies the configured mappings to the types.
This command needs providers to insert new documents in the elasticsearch types.
There are 2 ways to create providers.
If your elasticsearch type matches a Doctrine repository or a Propel query, go for the persistence automatic provider.
Or, for complete flexibility, go for manual provider.
#### Persistence automatic provider
If we want to index the entities from a Doctrine repository or a Propel query,
some configuration will let ElasticaBundle do it for us.
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
username: { boost: 5 }
firstName: { boost: 3 }
# more mappings...
persistence:
driver: orm # orm, mongodb, propel are available
model: Application\UserBundle\Entity\User
provider: ~
Three drivers are actually supported: orm, mongodb, and propel.
##### Use a custom Doctrine query builder
You can control which entities will be indexed by specifying a custom query builder method.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider:
query_builder_method: createIsActiveQueryBuilder
Your repository must implement this method and return a Doctrine query builder.
> **Propel** doesn't support this feature yet.
##### Change the batch size
By default, ElasticaBundle will index documents by packets of 100.
You can change this value in the provider configuration.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider:
batch_size: 100
##### Change the document identifier field
By default, ElasticaBundle will use the `id` field of your entities as the elasticsearch document identifier.
You can change this value in the persistence configuration.
persistence:
driver: orm
model: Application\UserBundle\Entity\User
identifier: id
#### Manual provider
Create a service with the tag "fos_elastica.provider" and attributes for the
index and type for which the service will provide.
<service id="acme.search_provider.user" class="Acme\UserBundle\Search\UserProvider">
<tag name="fos_elastica.provider" index="website" type="user" />
<argument type="service" id="fos_elastica.index.website.user" />
</service>
Its class must implement `FOS\ElasticaBundle\Provider\ProviderInterface`.
<?php
namespace Acme\UserBundle\Provider;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Elastica_Type;
class UserProvider implements ProviderInterface
{
protected $userType;
public function __construct(Elastica_Type $userType)
{
$this->userType = $userType;
}
/**
* Insert the repository objects in the type index
*
* @param Closure $loggerClosure
*/
public function populate(Closure $loggerClosure = null)
{
if ($loggerClosure) {
$loggerClosure('Indexing users');
}
$document = new \Elastica_Document();
$document->setData(array('username' => 'Bob'));
$this->userType->addDocuments(array($document));
}
}
You will find a more complete implementation example in `src/FOS/ElasticaBundle/Doctrine/AbstractProvider.php`.
### Search
You can just use the index and type Elastica objects, provided as services, to perform searches.
/** var Elastica_Type */
$userType = $this->container->get('fos_elastica.index.website.user');
/** var Elastica_ResultSet */
$resultSet = $userType->search('bob');
#### Doctrine/Propel finder
If your elasticsearch type is bound to a Doctrine entity repository or a Propel query,
you can get your entities instead of Elastica results when you perform a search.
Declare that you want a Doctrine/Propel finder in your configuration:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
You can now use the `fos_elastica.finder.website.user` service:
/** var FOS\ElasticaBundle\Finder\TransformedFinder */
$finder = $container->get('fos_elastica.finder.website.user');
/** var array of Acme\UserBundle\Entity\User */
$users = $finder->find('bob');
/** var array of Acme\UserBundle\Entity\User limited to 10 results */
$users = $finder->find('bob', 10);
You can even get paginated results!
Pagerfanta:
/** var Pagerfanta\Pagerfanta */
$userPaginator = $finder->findPaginated('bob');
Knp paginator:
$paginator = $this->get('knp_paginator');
$userPaginator = $paginator->paginate($finder->createPaginatorAdapter('bob'));
You can also get both the Elastica results and the entities together from the finder.
You can then access the score, highlights etc. from the Elastica_Result whilst
still also getting the entity.
/** var array of FOS\ElasticaBundle\HybridResult */
$hybridResults = $finder->findHybrid('bob');
foreach ($hybridResults as $hybridResult) {
/** var Acme\UserBundle\Entity\User */
$user = $hybridResult->getTransformed();
/** var Elastica_Result */
$result = $hybridResult->getResult();
}
##### Index wide finder
You can also define a finder that will work on the entire index. Adjust your index
configuration as per below:
fos_elastica:
indexes:
website:
client: default
finder: ~
You can now use the index wide finder service `fos_elastica.finder.website`:
/** var FOS\ElasticaBundle\Finder\MappedFinder */
$finder = $container->get('fos_elastica.finder.website');
// Returns a mixed array of any objects mapped
$results = $finder->find('bob');
#### Repositories
As well as using the finder service for a particular Doctrine/Propel entity you
can use a manager service for each driver and get a repository for an entity to search
against. This allows you to use the same service rather than the particular finder. For
example:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager.orm');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->find('bob');
You can also specify the full name of the entity instead of the shortcut syntax:
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('Application\UserBundle\Entity\User');
> The **2.0** branch doesn't support using `UserBundle:User` style syntax and you must use the full name of the entity. .
##### Default Manager
If you are only using one driver then its manager service is automatically aliased
to `fos_elastica.manager`. So the above example could be simplified to:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->find('bob');
If you use multiple drivers then you can choose which one is aliased to `fos_elastica.manager`
using the `default_manager` parameter:
fos_elastica:
default_manager: mongodb #defauults to orm
clients:
default: { host: localhost, port: 9200 }
#--
##### Custom Repositories
As well as the default repository you can create a custom repository for an entity and add
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
```
<?php
namespace Acme\ElasticaBundle\SearchRepository;
use FOS\ElasticaBundle\Repository;
class UserRepository extends Repository
{
public function findWithCustomQuery($searchText)
{
// build $query with Elastica objects
$this->find($query);
}
}
```
To use the custom repository specify it in the mapping for the entity:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
Then the custom queries will be available when using the repository returned from the manager:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** 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
namespace Application\UserBundle\Entity;
use FOS\ElasticaBundle\Configuration\Search;
/**
* @Search(repositoryClass="Acme\ElasticaBundle\SearchRepository\UserRepository")
*/
class User
{
//---
}
```
### Realtime, selective index update
If you use the Doctrine integration, you can let ElasticaBundle update the indexes automatically
when an object is added, updated or removed. It uses Doctrine lifecycle events.
Declare that you want to update the index in real time:
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
listener: # by default, listens to "insert", "update" and "delete"
Now the index is automatically updated each time the state of the bound Doctrine repository changes.
No need to repopulate the whole "user" index when a new `User` is created.
You can also choose to only listen for some of the events:
persistence:
listener:
insert: true
update: false
delete: true
> **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:
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:
persistence:
listener:
is_indexable_callback: [ "%custom_service_id%", "isIndexable" ]
In this case, the callback 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).
As you might expect, new entities will only be indexed if the callback returns
`true`. Additionally, modified entities will be updated or removed from the
index depending on whether the callback returns `true` or `false`, respectively.
The delete listener disregards the callback.
> **Propel** doesn't support this feature yet.
### Advanced elasticsearch configuration
Any setting can be specified when declaring a type. For example, to enable a custom analyzer, you could write:
fos_elastica:
indexes:
doc:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: custom
tokenizer: lowercase
filter : [my_ngram]
filter:
my_ngram:
type: "nGram"
min_gram: 3
max_gram: 5
types:
blog:
mappings:
title: { boost: 8, analyzer: my_analyzer }
### Overriding the Client class to suppress exceptions
By default, exceptions from the Elastica client library will propagate through
the bundle's Client class. For instance, if the elasticsearch server is offline,
issuing a request will result in an `Elastica_Exception_Client` being thrown.
Depending on your needs, it may be desirable to suppress these exceptions and
allow searches to fail silently.
One way to achieve this is to override the `fos_elastica.client.class` service
container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
```
<?php
namespace Acme\ElasticaBundle;
use FOS\ElasticaBundle\Client as BaseClient;
class Client extends BaseClient
{
public function request($path, $method, $data = array())
{
try {
return parent::request($path, $method, $data);
} catch (\Elastica_Exception_Abstract $e) {
return new \Elastica_Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
}
```
### Example of Advanced Query
If you would like to perform more advanced queries, here is one example using
the snowball stemming algorithm.
It searches for Article entities using `title`, `tags`, and `categoryIds`.
Results must match at least one specified `categoryIds`, and should match the
`title` or `tags` criteria. Additionally, we define a snowball analyzer to
apply to queries against the `title` field.
```php
$finder = $this->container->get('fos_elastica.finder.website.article');
$boolQuery = new \Elastica_Query_Bool();
$fieldQuery = new \Elastica_Query_Text();
$fieldQuery->setFieldQuery('title', 'I am a title string');
$fieldQuery->setFieldParam('title', 'analyzer', 'my_analyzer');
$boolQuery->addShould($fieldQuery);
$tagsQuery = new \Elastica_Query_Terms();
$tagsQuery->setTerms('tags', array('tag1', 'tag2'));
$boolQuery->addShould($tagsQuery);
$categoryQuery = new \Elastica_Query_Terms();
$categoryQuery->setTerms('categoryIds', array('1', '2', '3'));
$boolQuery->addMust($categoryQuery);
$data = $finder->find($boolQuery);
```
Configuration:
```yaml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
site:
settings:
index:
analysis:
analyzer:
my_analyzer:
type: snowball
language: English
types:
article:
mappings:
title: { boost: 10, analyzer: my_analyzer }
tags:
categoryIds:
persistence:
driver: orm
model: Acme\DemoBundle\Entity\Article
provider:
finder:
```

View file

@ -7,7 +7,7 @@ use FOS\ElasticaBundle\Finder\PaginatedFinderInterface;
/**
* @author Richard Miller <info@limethinking.co.uk>
*
* Basic repository to be extended to hold custom queries to be run
* Basic respoitory to be extended to hold custom queries to be run
* in the finder.
*/
class Repository
@ -19,49 +19,23 @@ class Repository
$this->finder = $finder;
}
/**
* @param mixed $query
* @param integer $limit
* @param array $options
*
* @return array
*/
public function find($query, $limit = null, $options = array())
public function find($query, $limit=null)
{
return $this->finder->find($query, $limit, $options);
return $this->finder->find($query, $limit);
}
/**
* @param mixed $query
* @param integer $limit
* @param array $options
*
* @return mixed
*/
public function findHybrid($query, $limit = null, $options = array())
public function findHybrid($query, $limit=null)
{
return $this->finder->findHybrid($query, $limit, $options);
return $this->finder->findHybrid($query, $limit);
}
/**
* @param mixed $query
* @param array $options
*
* @return \Pagerfanta\Pagerfanta
*/
public function findPaginated($query, $options = array())
public function findPaginated($query)
{
return $this->finder->findPaginated($query, $options);
return $this->finder->findPaginated($query);
}
/**
* @param string $query
* @param array $options
*
* @return Paginator\PaginatorAdapterInterface
*/
public function createPaginatorAdapter($query, $options = array())
public function createPaginatorAdapter($query)
{
return $this->finder->createPaginatorAdapter($query, $options);
return $this->finder->createPaginatorAdapter($query);
}
}

View file

@ -2,11 +2,100 @@
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\Index\Resetter as BaseResetter;
/**
* @deprecated Use \FOS\ElasticaBundle\Index\Resetter
* Deletes and recreates indexes
*/
class Resetter extends BaseResetter
class Resetter
{
protected $indexConfigsByName;
/**
* Constructor.
*
* @param array $indexConfigsByName
*/
public function __construct(array $indexConfigsByName)
{
$this->indexConfigsByName = $indexConfigsByName;
}
/**
* Deletes and recreates all indexes
*/
public function resetAllIndexes()
{
foreach ($this->indexConfigsByName as $indexConfig) {
$indexConfig['index']->create($indexConfig['config'], true);
}
}
/**
* Deletes and recreates the named index
*
* @param string $indexName
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function resetIndex($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$indexConfig['index']->create($indexConfig['config'], true);
}
/**
* 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
*/
public function resetIndexType($indexName, $typeName)
{
$indexConfig = $this->getIndexConfig($indexName);
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);
$type->delete();
$mapping = $this->createMapping($indexConfig['config']['mappings'][$typeName]);
$type->setMapping($mapping);
}
/**
* create type mapping object
*
* @param array $indexConfig
* @return \Elastica_Type_Mapping
*/
protected function createMapping($indexConfig)
{
$mapping = \Elastica_Type_Mapping::create($indexConfig['properties']);
foreach($indexConfig['properties'] as $type) {
if (!empty($type['_parent']) && $type['_parent'] !== '~') {
$mapping->setParam('_parent', array('type' => $type['_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];
}
}

View file

@ -1,51 +1,73 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
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">
<parameters>
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Elastica\Client</parameter>
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Client</parameter>
<parameter key="fos_elastica.index.class">Elastica_Index</parameter>
<parameter key="fos_elastica.type.class">Elastica_Type</parameter>
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
<parameter key="fos_elastica.mapping_builder.class">FOS\ElasticaBundle\Index\MappingBuilder</parameter>
<parameter key="fos_elastica.property_accessor.class">Symfony\Component\PropertyAccess\PropertyAccessor</parameter>
<parameter key="fos_elastica.manager.class">FOS\ElasticaBundle\Manager\RepositoryManager</parameter>
<parameter key="fos_elastica.elastica_to_model_transformer.collection.class">FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
<parameter key="fos_elastica.provider_registry.class">FOS\ElasticaBundle\Provider\ProviderRegistry</parameter>
</parameters>
<services>
<service id="fos_elastica.client_prototype" class="%fos_elastica.client.class%" abstract="true">
<argument type="collection" /> <!-- configuration -->
<argument /> <!-- callback -->
<call method="setStopwatch">
<argument type="service" id="debug.stopwatch" on-invalid="null" />
</call>
</service>
<service id="fos_elastica.config_manager" class="FOS\ElasticaBundle\Configuration\ConfigManager">
<argument type="collection" /> <!-- collection of SourceInterface services -->
</service>
<service id="fos_elastica.data_collector" class="%fos_elastica.data_collector.class%">
<tag name="data_collector" template="FOSElasticaBundle:Collector:elastica" id="elastica" />
<argument type="service" id="fos_elastica.logger" />
</service>
<service id="fos_elastica.paginator.subscriber" class="FOS\ElasticaBundle\Subscriber\PaginateElasticaQuerySubscriber">
<call method="setRequest">
<argument type="service" id="request" on-invalid="null" strict="false" />
</call>
<tag name="knp_paginator.subscriber" />
</service>
<service id="fos_elastica.logger" class="%fos_elastica.logger.class%">
<argument type="service" id="logger" on-invalid="null" />
<argument>%kernel.debug%</argument>
<tag name="monolog.logger" channel="elastica" />
</service>
<service id="fos_elastica.data_collector" class="%fos_elastica.data_collector.class%" public="true">
<tag name="data_collector" template="FOSElasticaBundle:Collector:elastica" id="elastica" />
<argument type="service" id="fos_elastica.logger" />
</service>
<service id="fos_elastica.mapping_builder" class="%fos_elastica.mapping_builder.class%" />
<service id="fos_elastica.index_manager" class="FOS\ElasticaBundle\IndexManager">
<argument /> <!-- indexes -->
<argument /> <!-- default index -->
</service>
<service id="fos_elastica.property_accessor" class="%fos_elastica.property_accessor.class%" />
<service id="fos_elastica.resetter" class="FOS\ElasticaBundle\Resetter">
<argument /> <!-- index configs -->
</service>
<service id="fos_elastica.object_persister" class="FOS\ElasticaBundle\Persister\ObjectPersister" abstract="true">
<argument /> <!-- type -->
<argument /> <!-- model to elastica transformer -->
<argument /> <!-- model -->
<argument /> <!-- properties mapping -->
</service>
<service id="fos_elastica.finder" class="FOS\ElasticaBundle\Finder\TransformedFinder" public="true" abstract="true">
<argument /> <!-- searchable -->
<argument /> <!-- transformer -->
</service>
<service id="fos_elastica.model_to_elastica_transformer" class="FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer" public="false" abstract="true">
<argument /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="property_accessor" on-invalid="null" />
</call>
</service>
<service id="fos_elastica.elastica_to_model_transformer.collection" class="%fos_elastica.elastica_to_model_transformer.collection.class%" public="true" abstract="true">
<argument type="collection" /> <!-- transformers -->
</service>
<service id="fos_elastica.provider_registry" class="%fos_elastica.provider_registry.class%">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
<service id="fos_elastica.paginator.subscriber" class="FOS\ElasticaBundle\Subscriber\PaginateElasticaQuerySubscriber">
<tag name="knp_paginator.subscriber" />
</service>
</services>
</container>

View file

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<parameters>
<parameter key="fos_elastica.alias_processor.class">FOS\ElasticaBundle\Index\AliasProcessor</parameter>
<parameter key="fos_elastica.finder.class">FOS\ElasticaBundle\Finder\TransformedFinder</parameter>
<parameter key="fos_elastica.index.class">FOS\ElasticaBundle\Elastica\Index</parameter>
<parameter key="fos_elastica.indexable.class">FOS\ElasticaBundle\Provider\Indexable</parameter>
<parameter key="fos_elastica.index_manager.class">FOS\ElasticaBundle\Index\IndexManager</parameter>
<parameter key="fos_elastica.resetter.class">FOS\ElasticaBundle\Index\Resetter</parameter>
<parameter key="fos_elastica.type.class">Elastica\Type</parameter>
</parameters>
<services>
<service id="fos_elastica.alias_processor" class="%fos_elastica.alias_processor.class%" />
<service id="fos_elastica.indexable" class="%fos_elastica.indexable.class%">
<argument type="collection" /> <!-- array of indexable callbacks keyed by type name -->
<argument type="service" id="service_container" />
</service>
<service id="fos_elastica.index_prototype" class="%fos_elastica.index.class%" factory-service="fos_elastica.client" factory-method="getIndex" abstract="true">
<argument /> <!-- index name -->
<!-- tagged with fos_elastica.index in the Extension -->
</service>
<service id="fos_elastica.type_prototype" class="%fos_elastica.type.class%" factory-method="getType" abstract="true">
<argument /> <!-- type name -->
</service>
<service id="fos_elastica.index_manager" class="%fos_elastica.index_manager.class%">
<argument /> <!-- indexes -->
<argument type="service" id="fos_elastica.index" /> <!-- default index -->
</service>
<service id="fos_elastica.resetter" class="%fos_elastica.resetter.class%">
<argument type="service" id="fos_elastica.config_manager" />
<argument type="service" id="fos_elastica.index_manager" />
<argument type="service" id="fos_elastica.alias_processor" />
<argument type="service" id="fos_elastica.mapping_builder" />
<argument type="service" id="event_dispatcher"/>
</service>
<!-- Abstract definition for all finders. -->
<service id="fos_elastica.finder" class="%fos_elastica.finder.class%" public="true" abstract="true">
<argument /> <!-- searchable -->
<argument /> <!-- transformer -->
</service>
</services>
</container>

View file

@ -4,46 +4,33 @@
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">
<parameters>
<parameter key="fos_elastica.slice_fetcher.mongodb.class">FOS\ElasticaBundle\Doctrine\MongoDB\SliceFetcher</parameter>
<parameter key="fos_elastica.provider.prototype.mongodb.class">FOS\ElasticaBundle\Doctrine\MongoDB\Provider</parameter>
<parameter key="fos_elastica.listener.prototype.mongodb.class">FOS\ElasticaBundle\Doctrine\Listener</parameter>
<parameter key="fos_elastica.elastica_to_model_transformer.prototype.mongodb.class">FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer</parameter>
<parameter key="fos_elastica.manager.mongodb.class">FOS\ElasticaBundle\Doctrine\RepositoryManager</parameter>
</parameters>
<services>
<service id="fos_elastica.slice_fetcher.mongodb" class="%fos_elastica.slice_fetcher.mongodb.class%">
</service>
<service id="fos_elastica.provider.prototype.mongodb" class="%fos_elastica.provider.prototype.mongodb.class%" public="true" abstract="true">
<service id="fos_elastica.provider.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine_mongodb" /> <!-- manager registry -->
<argument type="service" id="fos_elastica.slice_fetcher.mongodb" /> <!-- slice fetcher -->
<argument type="service" id="doctrine_mongodb" />
</service>
<service id="fos_elastica.listener.prototype.mongodb" class="%fos_elastica.listener.prototype.mongodb.class%" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Listener" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument/> <!-- identifier -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="%fos_elastica.elastica_to_model_transformer.prototype.mongodb.class%" public="false" abstract="true">
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false">
<argument type="service" id="doctrine_mongodb" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.manager.mongodb" class="%fos_elastica.manager.mongodb.class%">
<service id="fos_elastica.manager.mongodb" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<argument type="service" id="doctrine_mongodb"/>
<argument type="service" id="annotation_reader"/>
</service>
</services>
</container>

View file

@ -1,49 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<parameters>
<parameter key="fos_elastica.slice_fetcher.orm.class">FOS\ElasticaBundle\Doctrine\ORM\SliceFetcher</parameter>
<parameter key="fos_elastica.provider.prototype.orm.class">FOS\ElasticaBundle\Doctrine\ORM\Provider</parameter>
<parameter key="fos_elastica.listener.prototype.orm.class">FOS\ElasticaBundle\Doctrine\Listener</parameter>
<parameter key="fos_elastica.elastica_to_model_transformer.prototype.orm.class">FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer</parameter>
<parameter key="fos_elastica.manager.orm.class">FOS\ElasticaBundle\Doctrine\RepositoryManager</parameter>
</parameters>
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">
<services>
<service id="fos_elastica.slice_fetcher.orm" class="%fos_elastica.slice_fetcher.orm.class%">
</service>
<service id="fos_elastica.provider.prototype.orm" class="%fos_elastica.provider.prototype.orm.class%" public="true" abstract="true">
<service id="fos_elastica.provider.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine" /> <!-- manager registry -->
<argument type="service" id="fos_elastica.slice_fetcher.orm" /> <!-- slice fetcher -->
<argument type="service" id="doctrine" />
</service>
<service id="fos_elastica.listener.prototype.orm" class="%fos_elastica.listener.prototype.orm.class%" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Listener" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument/> <!-- identifier -->
<argument /> <!-- check method -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="%fos_elastica.elastica_to_model_transformer.prototype.orm.class%" public="false" abstract="true">
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer" public="false">
<argument type="service" id="doctrine" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.manager.orm" class="%fos_elastica.manager.orm.class%">
<argument type="service" id="doctrine" />
<argument type="service" id="annotation_reader" />
<service id="fos_elastica.manager.orm" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<argument type="service" id="doctrine"/>
<argument type="service" id="annotation_reader"/>
</service>
</services>
</container>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<parameters>
<parameter key="fos_elastica.object_persister.class">FOS\ElasticaBundle\Persister\ObjectPersister</parameter>
<parameter key="fos_elastica.object_serializer_persister.class">FOS\ElasticaBundle\Persister\ObjectSerializerPersister</parameter>
</parameters>
<services>
<service id="fos_elastica.object_persister" class="%fos_elastica.object_persister.class%" abstract="true">
<argument /> <!-- type -->
<argument /> <!-- model to elastica transformer -->
<argument /> <!-- model -->
<argument /> <!-- properties mapping -->
</service>
<service id="fos_elastica.object_serializer_persister" class="%fos_elastica.object_serializer_persister.class%" abstract="true">
<argument /> <!-- type -->
<argument /> <!-- model to elastica transformer -->
<argument /> <!-- model -->
<argument /> <!-- serializer -->
</service>
</services>
</container>

View file

@ -4,9 +4,9 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="fos_elastica.provider.prototype.propel" class="FOS\ElasticaBundle\Propel\Provider" public="true" abstract="true">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
</service>
@ -14,13 +14,12 @@
<service id="fos_elastica.elastica_to_model_transformer.prototype.propel" class="FOS\ElasticaBundle\Propel\ElasticaToModelTransformer" public="false">
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.manager.propel" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<service id="fos_elastica.manager.propel" class="%fos_elastica.manager.class%">
<argument type="service" id="annotation_reader"/>
</service>
</services>
</container>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<parameters>
<parameter key="fos_elastica.provider_registry.class">FOS\ElasticaBundle\Provider\ProviderRegistry</parameter>
</parameters>
<services>
<service id="fos_elastica.provider_registry" class="%fos_elastica.provider_registry.class%">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
</services>
</container>

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<services>
<service id="fos_elastica.serializer_callback_prototype" public="false" abstract="true">
<call method="setSerializer">
<argument type="service" id="fos_elastica.serializer" />
</call>
</service>
</services>
</container>

View file

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<services>
<service id="fos_elastica.config_source.container" class="FOS\ElasticaBundle\Configuration\Source\ContainerSource" public="false">
<argument type="collection" /> <!-- index configs -->
<tag name="fos_elastica.config_source" />
</service>
</services>
</container>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
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">
<parameters>
<parameter key="fos_elastica.elastica_to_model_transformer.collection.class">FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
<parameter key="fos_elastica.model_to_elastica_transformer.class">FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer</parameter>
<parameter key="fos_elastica.model_to_elastica_identifier_transformer.class">FOS\ElasticaBundle\Transformer\ModelToElasticaIdentifierTransformer</parameter>
</parameters>
<services>
<service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true">
<argument type="collection" /> <!-- options -->
<argument type="service" id="event_dispatcher" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.model_to_elastica_identifier_transformer" class="%fos_elastica.model_to_elastica_identifier_transformer.class%" public="false" abstract="true">
<argument type="collection" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>
</service>
<service id="fos_elastica.elastica_to_model_transformer.collection" class="%fos_elastica.elastica_to_model_transformer.collection.class%" public="true" abstract="true">
<argument type="collection" /> <!-- transformers -->
</service>
</services>
</container>

View file

@ -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" } }
]
}'
```

View file

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

View file

@ -1,76 +0,0 @@
##### Custom Repositories
As well as the default repository you can create a custom repository for an entity and add
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
```php
<?php
namespace Acme\ElasticaBundle\SearchRepository;
use FOS\ElasticaBundle\Repository;
class UserRepository extends Repository
{
public function findWithCustomQuery($searchText)
{
// build $query with Elastica objects
$this->find($query);
}
}
```
To use the custom repository specify it in the mapping for the entity:
```yaml
fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
website:
client: default
types:
user:
mappings:
# your mappings
persistence:
driver: orm
model: Application\UserBundle\Entity\User
provider: ~
finder: ~
repository: Acme\ElasticaBundle\SearchRepository\UserRepository
```
Then the custom queries will be available when using the repository returned from the manager:
```php
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->findWithCustomQuery('bob');
```
Alternatively you can specify the custom repository using an annotation in the entity:
```php
<?php
namespace Application\UserBundle\Entity;
use FOS\ElasticaBundle\Annotation\Search;
/**
* @Search(repositoryClass="Acme\ElasticaBundle\SearchRepository\UserRepository")
*/
class User
{
//---
}
```

View file

@ -1,18 +0,0 @@
Setting HTTP Headers on the Elastica Client
===========================================
It may be necessary to set HTTP headers on the Elastica client, for example an
Authorization header.
They can be set using the headers configuration key:
```yaml
# app/config/config.yml
fos_elastica:
clients:
default:
host: example.com
port: 80
headers:
Authorization: "Basic jdumrGK7rY9TMuQOPng7GZycmxyMHNoir=="
```

View file

@ -1,35 +0,0 @@
Logging and its performance considerations
==========================================
By default, FOSElasticaBundle sets a logger against each Elastica client configured and
logs all information sent to and received from Elasticsearch. This can lead to large
memory usage during population or reindexing of an index.
By default FOSElasticaBundle will only enable a logger when debug mode is enabled, meaning
in a production environment there wont be a logger enabled. To enable a logger anyway, you
can set the logger property of a client configuration to true or a service id of a logging
service you wish to use.
```yaml
# app/config/config.yml
fos_elastica:
clients:
default:
host: example.com
logger: true
```
Custom Logger Service
---------------------
It is also possible to specify a custom logger instance to be injected into each client by
specifying the service id of the logger you wish to use.
```yaml
# app/config/config.yml
fos_elastica:
clients:
default:
host: example.com
logger: 'acme.custom.logger'
```

View file

@ -1,56 +0,0 @@
Manual provider
===============
Create a service with the tag "fos_elastica.provider" and attributes for the
index and type for which the service will provide.
```yaml
# app/config/config.yml
services:
acme.search_provider.user:
class: Acme\UserBundle\Provider\UserProvider
arguments:
- @fos_elastica.index.website.user
tags:
- { name: fos_elastica.provider, index: website, type: user }
```
Its class must implement `FOS\ElasticaBundle\Provider\ProviderInterface`.
```php
namespace Acme\UserBundle\Provider;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Elastica\Type;
use Elastica\Document;
class UserProvider implements ProviderInterface
{
protected $userType;
public function __construct(Type $userType)
{
$this->userType = $userType;
}
/**
* Insert the repository objects in the type index
*
* @param \Closure $loggerClosure
* @param array $options
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
if ($loggerClosure) {
$loggerClosure('Indexing users');
}
$document = new Document();
$document->setData(array('username' => 'Bob'));
$this->userType->addDocuments(array($document));
}
}
```
You will find a more complete implementation example in `src/FOS/ElasticaBundle/Doctrine/AbstractProvider.php`.

View file

@ -1,21 +0,0 @@
Multiple Connections
====================
You can define multiple endpoints for an Elastica client by specifying them as
multiple connections in the client configuration:
```yaml
fos_elastica:
clients:
default:
connections:
- url: http://es1.example.net:9200
- url: http://es2.example.net:9200
connection_strategy: RoundRobin
```
Elastica allows for definition of different connection strategies and by default
supports `RoundRobin` and `Simple`. You can see definitions for these strategies
in the `Elastica\Connection\Strategy` namespace.
For more information on Elastica clustering see http://elastica.io/getting-started/installation.html#section-connect-cluster

View file

@ -1,59 +0,0 @@
Suppressing Server Errors
=========================
By default, exceptions from the Elastica client library will propagate through
the bundle's Client class. For instance, if the Elasticsearch server is offline,
issuing a request will result in an `Elastica\Exception\Connection` being thrown.
Depending on your needs, it may be desirable to suppress these exceptions and
allow searches to fail silently.
One way to achieve this is to override the `fos_elastica.client.class` service
container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
Sample client code:
-------------------
```php
<?php
namespace Acme\ElasticaBundle;
use Elastica\Exception\ExceptionInterface;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Elastica\Client as BaseClient;
class Client extends BaseClient
{
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
try {
return parent::request($path, $method, $data, $query);
} catch (ExceptionInterface $e) {
if ($this->_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
```

Some files were not shown because too many files have changed in this diff Show more