Compare commits

..

142 commits

Author SHA1 Message Date
Simon Vieille 30e351b1d5 Propel provider: query_builder_method set to null 2015-05-20 11:13:43 +02:00
Simon Vieille e3abbdc700 Propel provider: default options 2015-05-20 11:12:03 +02:00
Tim Nagel 4451bd07c6 Merge pull request #860 from kayue/patch-1
Count the number of results for the query directly
2015-04-28 12:56:12 +10:00
Tim Nagel 1f8a330140 Merge pull request #870 from davidfuhr/patch-1
Fixed parameter name to kernel.environment
2015-04-28 12:55:14 +10:00
David Fuhr 8f7f24e6d3 Fixed parameter nam to kernel.environment
The parameter %kernel.env% does not exist. It throws the error message 'You have requested a non-existent parameter "kernel.env". Did you mean this: "kernel.environment"?'
2015-04-27 23:00:50 +02:00
Ka Yue Yeung e71ec4ac8a Count the number of results for the query directly 2015-04-18 00:42:02 +08:00
Lukas Kahwe Smith adf7fb21e3 Merge pull request #848 from thierrymarianne/patch-1
Fix typo
2015-04-05 23:20:26 +02:00
Thierry Marianne 1287d9f0df Fix typo 2015-04-05 22:07:41 +02:00
Tim Nagel c5728b5870 Merge branch '3.1.x' 2015-04-02 10:39:05 +11:00
Tim Nagel 7baf494c56 release 3.1.3 2015-04-02 10:34:08 +11:00
Tim Nagel ad20382e08 Merge pull request #842 from merk/fix-provider-symfony-23
Fix symfony 2.3 compatibility with 3.1
2015-04-02 10:33:05 +11:00
Tim Nagel e933a49d07 Use deprecated optionsResolver interface 2015-04-02 10:23:30 +11:00
Tim Nagel 69470d7e20 Fix the previous release. 2015-03-27 12:01:39 +11:00
Tim Nagel b6e01cd332 Fix issues with Provider's batch_size and PopulateCommand's batch_size 2015-03-27 11:45:03 +11:00
Tim Nagel 35276f469a Merge pull request #835 from TomasVotruba/patch-2
Setup.md: fix link to Elasticsearch installation
2015-03-27 08:59:18 +11:00
Tim Nagel ec9f23bd8d Merge pull request #834 from TomasVotruba/patch-1
Setup.md - composer picks last stable version
2015-03-27 08:58:33 +11:00
Tomáš Votruba ae4cfd7e04 Setup.md: fix link to Elasticsearch installation 2015-03-26 15:05:28 +01:00
Tomáš Votruba 49a0c22724 Setup.md - composer picks last stable version 2015-03-26 15:03:20 +01:00
Tim Nagel 8d8b04ead8 Fixes populate command error 2015-03-23 21:39:13 +11:00
Tim Nagel a59f2015b4 Merge branch '3.1.x' 2015-03-18 09:40:06 +11:00
Tim Nagel 447d29ab9c Release 3.1.0 2015-03-18 09:38:47 +11:00
Tim Nagel 73093beadb Merge pull request #823 from merk/more-tests
More test coverage
2015-03-18 09:37:23 +11:00
Tim Nagel 5181b02933 Resetter tests 2015-03-14 22:14:24 +11:00
Tim Nagel ac98549eb5 Fix translation of option keys for the Provider 2015-03-14 19:54:23 +11:00
Tim Nagel 9c1c771799 Mark getSlice private 2015-03-14 19:54:01 +11:00
Tim Nagel 3bb2f384ba Provider refactoring 2015-03-14 19:53:05 +11:00
Tim Nagel d4f01e8d2e Cast result from ExpressionLanguage eval to bool 2015-03-14 18:48:59 +11:00
Tim Nagel 72a9dfa267 Indexable service improvements 2015-03-14 16:08:50 +11:00
Tim Nagel 9cf0117c71 AliasProcessor 2015-03-14 00:51:07 +11:00
Tim Nagel bb4618c101 Even more QA 2015-03-13 19:34:56 +11:00
Tim Nagel 6a07f7b24e More QA 2015-03-13 19:10:24 +11:00
Tim Nagel dd3269d1ef Merge branch 'code-qa' into 3.1.x 2015-03-13 15:17:52 +11:00
Tim Nagel 84e5831a81 Fixing scrutinizer issues 2015-03-13 14:57:36 +11:00
Tim Nagel d5a9b7b235 Add missing method to HighlightableModelInterface.
This is not a BC break - the method has always been required and lacking the method would cause a fatal error.
2015-03-13 14:56:07 +11:00
Tim Nagel 4af9f442fd Move common sorting code to base Transformer class 2015-03-13 14:56:07 +11:00
Christophe Coevoet 4081c32ca0 Merge branch '3.1.x' 2015-03-12 17:54:38 +01:00
Christophe Coevoet 925410a66e Merge branch '3.0.x' into 3.1.x 2015-03-12 17:54:30 +01:00
Christophe Coevoet 133f71b88a Merge branch '3.1.x' 2015-03-12 17:49:49 +01:00
Christophe Coevoet c013ed9657 Merge branch '3.0.x' into 3.1.x
Conflicts:
	Resources/config/orm.xml
2015-03-12 17:49:25 +01:00
Tim Nagel 4e087af50d Fix issues with CS merge 2015-03-12 22:41:48 +11:00
Tim Nagel 559b14b4a5 Merge branch '3.1.x' 2015-03-12 22:02:06 +11:00
Tim Nagel 89db88c2a0 CS fixes for 3.1 2015-03-12 21:58:02 +11:00
Tim Nagel 345b5d423d Merge cs-fixes-30 into cs-fixes-31 2015-03-12 21:57:26 +11:00
Tim Nagel a8f41fa5ef We do not provide API guarantees 2015-03-12 21:47:19 +11:00
Tim Nagel 81186e40db Bump master to 3.2-dev 2015-03-12 20:36:50 +11:00
Tim Nagel 14af748840 Fix bad merge 2015-03-12 18:22:04 +11:00
Tim Nagel 19e9abaa53 Update documentation links for 3.1 and 3.0 2015-03-11 22:21:56 +11:00
Tim Nagel 5b88baeca6 Merge branch 'pr/744'
Conflicts:
	CHANGELOG-3.1.md
	Command/PopulateCommand.php
2015-03-11 22:17:14 +11:00
Tim Nagel b7c7f77383 Merge branch 'pr/725'
Conflicts:
	CHANGELOG-3.1.md
	Doctrine/AbstractProvider.php
2015-03-11 22:10:54 +11:00
Tim Nagel 181b5a0ac0 Merge pull request #815 from merk/command-tidy
Command tidy
2015-03-11 22:09:00 +11:00
Tim Nagel 0009c858a7 Fixes for errors when with the progress closure 2015-03-11 21:31:23 +11:00
Tim Nagel f834499a2c Fixes after review 2015-03-11 15:54:04 +11:00
Tim Nagel 3d69c08b5a Merge branch 'master' into pr/725
Conflicts:
	Doctrine/AbstractProvider.php
2015-03-11 15:47:03 +11:00
Tim Nagel cbb247978a Update changeling for aggregations 2015-03-11 15:37:34 +11:00
Tim Nagel a830a9b7b6 Merge pull request #726 from cassianotartari/master
Add aggregations
2015-03-11 15:34:47 +11:00
Tim Nagel 7b90c84daa Merge pull request #728 from ugomeda/master
Add missing KnpPaginator example in the usage documentation
2015-03-11 15:33:03 +11:00
Tim Nagel 034b3a9c60 Merge branch '3.0.x' 2015-03-11 15:30:09 +11:00
Tim Nagel 4a564401b4 Symfony <=2.5 uses the legacy progress closure 2015-03-11 15:17:06 +11:00
Tim Nagel ef2671dd36 Moved the progress helper closure building to a dedicated class 2015-03-11 15:11:42 +11:00
Tim Nagel 47785260a4 Fix an error that PopulateCommand would always ignore errors 2015-03-11 15:11:13 +11:00
Tim Nagel cb7b4c1dca Configurable ProgressBar format definition overrides 2015-03-11 15:09:48 +11:00
Tim Nagel 4c87d24fc1 Changes after review 2015-03-11 14:55:13 +11:00
Tim Nagel f6df88cc67 Merge branch 'master' into pr/744
Conflicts:
	Command/PopulateCommand.php
2015-03-11 14:11:18 +11:00
Tim Nagel 7fa14f713c Merge branch '3.0.x' 2015-03-10 22:01:24 +11:00
Tim Nagel 07995d9b75 Merge pull request #794 from merk/property-paths
Added capability to define property paths
2015-03-10 21:58:32 +11:00
Tim Nagel 72a981ab51 Attempted fix for php 5.3 2015-03-10 21:49:26 +11:00
Tim Nagel 1be1cb645c Merge pull request #809 from chtipepere/patch-1
Update usage.md
2015-03-10 21:13:21 +11:00
Marichez Pierre 70fe702ccf Update usage.md
Fix indent in yml
2015-03-07 22:06:32 +01:00
WouterJ.nl bot 3c87cc16c7 Merge branch '3.0.x' 2015-02-18 11:38:02 +01:00
Tim Nagel c5185a0307 Added capability to define property paths 2015-02-09 09:40:49 +11:00
Tim Nagel e1d5ef72d2 Merge pull request #792 from FriendsOfSymfony/progress-bar-bc
Additional change for 2.5 ProgressBar support
2015-02-07 00:03:50 +11:00
Tim Nagel 797d066286 Additional change for 2.5 ProgressBar support
getProgress() is not available and getStep() throws a deprecation warning.
2015-02-06 23:50:39 +11:00
Tim Nagel 72589f8341 Fix ProgressBar for Symfony 2.5 2015-02-06 22:07:13 +11:00
Tim Nagel 7bc88494a1 Merge branch '3.0.x' 2015-01-27 08:21:57 +11:00
Tim Nagel 30d52bf0f0 Merge pull request #782 from allanbrault/patch-1
Update PopulateCommand.php
2015-01-27 08:08:31 +11:00
Allan Brault 58eed2dc7f Update PopulateCommand.php
in the case i have 110 objects, it was doing :
Populating abc/Index 90.9% (100/110)
Populating abc/index 181.8% (200/110)

now :
Populating abc/Index 90.9% (100/110)
Populating abc/index 100.0% (110/110)
2015-01-26 16:59:55 +01:00
Tim Nagel d44525f6f3 Merge pull request #780 from merk/pr/770
Continued work on ProgressBar
2015-01-25 19:54:53 +11:00
Tim Nagel 67c0b79505 Tidy up ProgressBar use, move most calculations for loggerClosure
into PopulateCommand rather than in AbstractProvider
2015-01-25 19:53:58 +11:00
Pablo Martelletti 6bb2def21e Use ProgressBar in populate commande when available. 2015-01-25 19:53:06 +11:00
Tim Nagel 7f28be3c4e Merge branch '3.0.x' 2015-01-22 11:26:07 +11:00
Christophe Coevoet 1cea135dc5 Merge branch '3.0.x' 2015-01-21 18:11:18 +01:00
Tim Nagel 1bf59d5b66 Merge pull request #773 from merk/transformer-event
Dispatch an event when transforming objects
2015-01-21 13:29:23 +11:00
Tim Nagel 9188566dfe Dispatch an event when transforming objects 2015-01-20 14:41:11 +11:00
Tim Nagel d7e9d9b8a6 Merge pull request #718 from WouterJ/patch-1
Applied standard installation template
2015-01-12 10:48:42 +11:00
Wouter J a28b9d3069 Applied standard installation template 2015-01-11 17:48:13 +01:00
Tim Nagel c2c87a53e4 Merge branch '3.0.x' 2015-01-09 08:56:41 +11:00
Tim Nagel ee9f7e5297 Merge pull request #768 from vkartaviy/patch-1
Fix ODM listener service arguments
2015-01-09 08:44:44 +11:00
Vladimir Kartaviy a0f11ff36f Fix ODM listener service arguments
It became broken after d731443aa5 commit
2015-01-07 16:45:18 +02:00
Oleg Andreyev 7c90660e02 attempt to make scrutinizer happy, about duplicate code in test 2015-01-04 20:57:08 +02:00
Oleg Andreyev 7efcdad97c adding ResetEvent (pre/post index and pre/post type reset), injected EventDispatcher into Resetter 2015-01-04 14:51:21 +02:00
Oleg Andreyev afbe1e03a1 adding unit test for PopulateListener 2015-01-04 14:51:21 +02:00
Oleg Andreyev 303af508b2 adding populate events 2015-01-04 14:17:25 +02:00
Tim Nagel c39b86c6c5 Merge branch 'detection-config' 2015-01-04 22:12:21 +11:00
Tim Nagel b6d46dba4a Merge tag 'v3.0.6' 2015-01-04 22:10:18 +11:00
Tim Nagel 92aab4bcf6 Add PR# to changelog 2015-01-04 22:02:54 +11:00
Tim Nagel e361b7c53b Implement additional configuration options for types. 2015-01-04 21:58:53 +11:00
Tim Nagel 7fac93ff8b Fix mongodb doctrine listener 2015-01-04 20:16:32 +11:00
Dmitry Korotovsky d731443aa5 Avoid Doctrine\Listener::getSubscribedEvents() call on each page where doctrine is active 2015-01-04 20:05:47 +11:00
Christophe Coevoet f3e31e613e Merge branch '3.0.x'
Conflicts:
	.travis.yml

Duplicate testing on Symfony 2.6 is removed (it is already done by the
normal testing as it is the latest stable), and testing on 2.4 is
removed again because it is EOLed.
2014-12-17 14:25:15 +01:00
Tim Nagel 88e9f5aac6 Merge pull request #748 from NinjDS/totalhits
Genuine ES behaviour for getTotalHits()
2014-12-01 22:04:25 +11:00
Tim Nagel ca57c42489 Merge pull request #738 from FriendsOfSymfony/adjust-build-matrix
adjust to symfony 3.0 development having started
2014-12-01 22:02:10 +11:00
David Buchmann 2664fec35e adjust to symfony 3.0 development having started. whenever there is a new 2.x branch in symfony, we should add it to the matrix 2014-12-01 09:21:43 +01:00
Tim Nagel 7dc2f833c4 Merge branch '3.0.x' 2014-12-01 09:46:49 +11:00
Tim Nagel e211f31658 Merge pull request #743 from KingCrunch/patch-1
Update custom-repositories.md to use code highlighting
2014-12-01 09:36:45 +11:00
Tim Nagel 00df6c586f Merge pull request #737 from jaymecd/clean_filtered
Doctrine provider: memory allocation caused by "entire batch was filtered away"
2014-12-01 09:34:21 +11:00
Tim Nagel 921377d5ed Merge pull request #750 from javiereguiluz/update_installation_instructions
Updated installation instructions
2014-12-01 08:52:01 +11:00
Javier Eguiluz c4ee9fa83e Updated installation instructions 2014-11-29 17:17:31 +01:00
Adrien Morel b09bf3cf10 RawPaginatorAdapter::getTotalHits() can retrieve the genuine total hits returned by elasticsearch 2014-11-26 16:25:23 +01:00
Sebastian Krebs 1e2da2d84f Update custom-repositories.md to use code highlighting
Looks better this way
2014-11-21 14:21:26 +01:00
David Buchmann 2d8903a330 Merge pull request #736 from piotrantosik/patch-2
Fix changelog header
2014-11-14 15:59:43 +01:00
Nikolai Zujev e6d50c584c Clean filtered objects if the entire batch was filtered away, to prevent memory allocation issue. 2014-11-13 17:34:21 +02:00
Piotr Antosik 2a7459f327 Fix changelog header 2014-11-10 14:05:56 +01:00
Gnucki c80b4efd3e Merge branch master 2014-11-02 14:28:44 +01:00
Gnucki 1369a01dd7 Add slice fetching abstraction 2014-11-02 14:24:28 +01:00
Gnucki 901ea49a32 Factorize manager call 2014-11-02 14:22:26 +01:00
Ugo Méda 1b01aef46f Add missing KnpPaginator example in the usage documentation 2014-10-16 15:46:40 +02:00
Gnucki be75b387a5 Add slice fetching abstraction 2014-10-13 15:41:29 +02:00
Gnucki 445f2f93f6 Add slice fetching abstraction 2014-10-13 15:40:31 +02:00
Cassiano e7634d8ba2 Update RawPaginatorAdapter.php 2014-10-08 17:15:57 -03:00
Cassiano Tartari 419bf2ccf6 Merge remote-tracking branch 'upstream/master'
Conflicts:
	Paginator/RawPaginatorAdapter.php
2014-10-08 17:08:13 -03:00
Cassiano 6f4e389dfd Update RawPaginatorAdapter.php 2014-10-08 15:19:48 -03:00
Cassiano 196aed6630 Update PaginateElasticaQuerySubscriber.php 2014-10-08 14:33:15 -03:00
Cassiano 7c6fe4eaab Update FantaPaginatorAdapter.php 2014-10-08 14:32:24 -03:00
Cassiano d2de7ba6e8 Update PaginatorAdapterInterface.php 2014-10-08 14:31:39 -03:00
Cassiano 197bb3ebad Update PartialResultsInterface.php 2014-10-08 14:31:19 -03:00
Cassiano f9ce1dcd4e Update RawPaginatorAdapter.php 2014-10-08 14:30:03 -03:00
Cassiano 4c4e9ffe36 Update RawPartialResults.php 2014-10-08 14:27:50 -03:00
Gnucki 7fa7e44bee Fix mongodb populate falling down performances for big collections 2014-10-08 10:44:49 +02:00
Tim Nagel 25d56d0a0f 3.1.x requires doctrine 2.4+ 2014-10-03 08:21:02 +10:00
Tim Nagel a88e4e38f8 Merge branch 'cs-fixes-31' 2014-09-21 21:09:39 +10:00
Tim Nagel 5cdeac9b45 Merge branch '3.0.x' 2014-09-21 21:09:33 +10:00
Tim Nagel 71a86cada5 BC BREAK: Add handlesObject method to ObjectPersisterInterface 2014-09-21 20:25:32 +10:00
Tim Nagel d0ce82ac2a Adjust DoctrineListener to remove unnecessary method getDoctrineObject 2014-09-21 20:25:25 +10:00
Tim Nagel cf586a4ef4 Merge branch '3.0.x' 2014-09-04 09:39:54 +10:00
Tim Nagel 6bea3c2154 Add code quality badge 2014-08-21 23:23:20 +10:00
Tim Nagel 001daeeac0 Merge branch '3.0.x' 2014-08-21 23:17:42 +10:00
Tim Nagel c08d86124a Merge branch '3.0.x' 2014-08-11 08:44:22 +10:00
Tim Nagel 49c5b2ee63 Merge branch 'pr/685' 2014-08-08 21:37:20 +10:00
Oleg Andreyev 27385046ca changing AliasProcessor::setRootName, so to use "Y-m-d-His" instead of random string 2014-08-08 21:36:49 +10:00
Tim Nagel 517d6e679c Merge branch '3.0.x' 2014-08-08 21:31:14 +10:00
Tim Nagel 229d4cb982 Merge branch '3.0.x' 2014-08-08 08:36:36 +10:00
Tim Nagel 9d45de8b64 Merge pull request #684 from cygnusb2b/deep-merging-config
Configuration Bug: Restored noDeepMerging
2014-08-08 08:33:09 +10:00
76 changed files with 2370 additions and 752 deletions

61
CHANGELOG-3.1.md Normal file
View file

@ -0,0 +1,61 @@
CHANGELOG for 3.1.x
===================
This changelog references the relevant changes (bug and security fixes) done
in 3.1 versions.
To get the diff for a specific change, go to
https://github.com/FriendsOfSymfony/FOSElasticaBundle/commit/XXX where XXX is
the commit hash. To get the diff between two versions, go to
https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
* 3.1.3 (2015-04-02)
* Fix Symfony 2.3 compatibility
* 3.1.2 (2015-03-27)
* Fix the previous release
* 3.1.1 (2015-03-27)
* Fix PopulateCommand trying to set formats for ProgressBar in Symfony < 2.5
* Fix Provider implementations that depend on a batch size from going into
infinite loops
* 3.1.0 (2015-03-18)
* BC BREAK: `Doctrine\Listener#scheduleForDeletion` access changed to private.
* BC BREAK: `ObjectPersisterInterface` gains the method `handlesObject` that
returns a boolean value if it will handle a given object or not.
* BC BREAK: Removed `Doctrine\Listener#getSubscribedEvents`. The container
configuration now configures tags with the methods to call to avoid loading
this class on every request where doctrine is active. #729
* BC BREAK: Added methods for retrieving aggregations when paginating results.
The `PaginationAdapterInterface` has a new method, `getAggregations`. #726
* Added ability to configure `date_detection`, `numeric_detection` and
`dynamic_date_formats` for types. #753
* New event `POST_TRANSFORM` which allows developers to add custom properties to
Elastica Documents for indexing.
* When available, the `fos:elastica:populate` command will now use the
ProgressBar helper instead of outputting strings. You can use verbosity
controls on the command to output additional information like memory
usage, runtime and estimated time.
* Added new option `property_path` to a type property definition to allow
customisation of the property path used to retrieve data from objects.
Setting `property_path` to `false` will configure the Transformer to ignore
that property while transforming. Combined with the above POST_TRANSFORM event
developers can now create calculated dynamic properties on Elastica documents
for indexing. #794
* Fixed a case where ProgressCommand would always ignore errors regardless of
--ignore-errors being passed or not.
* Added a `SliceFetcher` abstraction for Doctrine providers that get more
information about the previous slice allowing for optimising queries during
population. #725
* New events `PRE_INDEX_POPULATE`, `POST_INDEX_POPULATE`, `PRE_TYPE_POPULATE` and
`POST_TYPE_POPULATE` allow for monitoring when an index is about to be or has
just been populated. #744
* New events `PRE_INDEX_RESET`, `POST_INDEX_RESET`, `PRE_TYPE_RESET` and
`POST_TYPE_RESET` are run before and after operations that will reset an
index. #744
* Added indexable callback support for the __invoke method of a service. #823

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Command;
use FOS\ElasticaBundle\Event\IndexPopulateEvent;
use FOS\ElasticaBundle\Event\TypePopulateEvent;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputOption;
@ -10,18 +12,28 @@ use Symfony\Component\Console\Output\OutputInterface;
use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Provider\ProviderRegistry;
use FOS\ElasticaBundle\Resetter;
use FOS\ElasticaBundle\Provider\ProviderInterface;
use Symfony\Component\Console\Helper\ProgressBar;
/**
* Populate the search index.
*/
class PopulateCommand extends ContainerAwareCommand
{
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
private $dispatcher;
/**
* @var IndexManager
*/
private $indexManager;
/**
* @var ProgressClosureBuilder
*/
private $progressClosureBuilder;
/**
* @var ProviderRegistry
*/
@ -46,31 +58,46 @@ class PopulateCommand extends ContainerAwareCommand
->addOption('sleep', null, InputOption::VALUE_REQUIRED, 'Sleep time between persisting iterations (microseconds)', 0)
->addOption('batch-size', null, InputOption::VALUE_REQUIRED, 'Index packet size (overrides provider config option)')
->addOption('ignore-errors', null, InputOption::VALUE_NONE, 'Do not stop on errors')
->addOption('no-overwrite-format', null, InputOption::VALUE_NONE, 'Prevent this command from overwriting ProgressBar\'s formats')
->setDescription('Populates search indexes from providers')
;
}
/**
* @see Symfony\Component\Console\Command\Command::initialize()
* {@inheritDoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->dispatcher = $this->getContainer()->get('event_dispatcher');
$this->indexManager = $this->getContainer()->get('fos_elastica.index_manager');
$this->providerRegistry = $this->getContainer()->get('fos_elastica.provider_registry');
$this->resetter = $this->getContainer()->get('fos_elastica.resetter');
$this->progressClosureBuilder = new ProgressClosureBuilder();
if (!$input->getOption('no-overwrite-format') && class_exists('Symfony\\Component\\Console\\Helper\\ProgressBar')) {
ProgressBar::setFormatDefinition('normal', " %current%/%max% [%bar%] %percent:3s%%\n%message%");
ProgressBar::setFormatDefinition('verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%\n%message%");
ProgressBar::setFormatDefinition('very_verbose', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%\n%message%");
ProgressBar::setFormatDefinition('debug', " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%\n%message%");
}
}
/**
* @see Symfony\Component\Console\Command\Command::execute()
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$index = $input->getOption('index');
$type = $input->getOption('type');
$reset = !$input->getOption('no-reset');
$options = $input->getOptions();
$options['ignore-errors'] = $input->hasOption('ignore-errors');
$index = $input->getOption('index');
$type = $input->getOption('type');
$reset = !$input->getOption('no-reset');
$options = array(
'ignore_errors' => $input->getOption('ignore-errors'),
'offset' => $input->getOption('offset'),
'sleep' => $input->getOption('sleep')
);
if ($input->getOption('batch-size')) {
$options['batch_size'] = (int) $input->getOption('batch-size');
}
if ($input->isInteractive() && $reset && $input->getOption('offset')) {
/** @var DialogHelper $dialog */
@ -109,25 +136,22 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndex(OutputInterface $output, $index, $reset, $options)
{
if ($reset) {
$event = new IndexPopulateEvent($index, $reset, $options);
$this->dispatcher->dispatch(IndexPopulateEvent::PRE_INDEX_POPULATE, $event);
if ($event->isReset()) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
$this->resetter->resetIndex($index, true);
}
/** @var $providers ProviderInterface[] */
$providers = $this->providerRegistry->getIndexProviders($index);
foreach ($providers as $type => $provider) {
$loggerClosure = function ($message) use ($output, $index, $type) {
$output->writeln(sprintf('<info>Populating</info> %s/%s, %s', $index, $type, $message));
};
$provider->populate($loggerClosure, $options);
$types = array_keys($this->providerRegistry->getIndexProviders($index));
foreach ($types as $type) {
$this->populateIndexType($output, $index, $type, false, $event->getOptions());
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->resetter->postPopulate($index);
$this->indexManager->getIndex($index)->refresh();
$this->dispatcher->dispatch(IndexPopulateEvent::POST_INDEX_POPULATE, $event);
$this->refreshIndex($output, $index);
}
/**
@ -141,17 +165,35 @@ class PopulateCommand extends ContainerAwareCommand
*/
private function populateIndexType(OutputInterface $output, $index, $type, $reset, $options)
{
if ($reset) {
$event = new TypePopulateEvent($index, $type, $reset, $options);
$this->dispatcher->dispatch(TypePopulateEvent::PRE_TYPE_POPULATE, $event);
if ($event->isReset()) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s/%s</comment>', $index, $type));
$this->resetter->resetIndexType($index, $type);
}
$loggerClosure = function ($message) use ($output, $index, $type) {
$output->writeln(sprintf('<info>Populating</info> %s/%s, %s', $index, $type, $message));
};
$provider = $this->providerRegistry->getProvider($index, $type);
$provider->populate($loggerClosure, $options);
$loggerClosure = $this->progressClosureBuilder->build($output, 'Populating', $index, $type);
$provider->populate($loggerClosure, $event->getOptions());
$this->dispatcher->dispatch(TypePopulateEvent::POST_TYPE_POPULATE, $event);
$this->refreshIndex($output, $index, false);
}
/**
* Refreshes an index.
*
* @param OutputInterface $output
* @param string $index
* @param bool $postPopulate
*/
private function refreshIndex(OutputInterface $output, $index, $postPopulate = true)
{
if ($postPopulate) {
$this->resetter->postPopulate($index);
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->indexManager->getIndex($index)->refresh();

View file

@ -0,0 +1,103 @@
<?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

@ -35,6 +35,22 @@ class TypeConfig
$this->name = $name;
}
/**
* @return bool|null
*/
public function getDateDetection()
{
return $this->getConfig('date_detection');
}
/**
* @return array
*/
public function getDynamicDateFormats()
{
return $this->getConfig('dynamic_date_formats');
}
/**
* @return string|null
*/
@ -61,6 +77,14 @@ class TypeConfig
null;
}
/**
* @return bool|null
*/
public function getNumericDetection()
{
return $this->getConfig('numeric_detection');
}
/**
* @return string
*/

View file

@ -250,7 +250,10 @@ class Configuration implements ConfigurationInterface
})
->end()
->children()
->booleanNode('date_detection')->end()
->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end()
->scalarNode('index_analyzer')->end()
->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end()
->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode())

View file

@ -246,6 +246,9 @@ class FOSElasticaExtension extends Extension
'serializer',
'index_analyzer',
'search_analyzer',
'date_detection',
'dynamic_date_formats',
'numeric_detection',
) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] :
@ -479,19 +482,30 @@ class FOSElasticaExtension extends Extension
$listenerId = sprintf('fos_elastica.listener.%s.%s', $indexName, $typeName);
$listenerDef = new DefinitionDecorator($abstractListenerId);
$listenerDef->replaceArgument(0, new Reference($objectPersisterId));
$listenerDef->replaceArgument(1, $this->getDoctrineEvents($typeConfig));
$listenerDef->replaceArgument(3, array(
$listenerDef->replaceArgument(2, array(
'identifier' => $typeConfig['identifier'],
'indexName' => $indexName,
'typeName' => $typeName,
));
if ($typeConfig['listener']['logger']) {
$listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
$listenerDef->replaceArgument(3, $typeConfig['listener']['logger'] ?
new Reference($typeConfig['listener']['logger']) :
null
);
$tagName = null;
switch ($typeConfig['driver']) {
case 'orm':
$tagName = 'doctrine.event_listener';
break;
case 'mongodb':
$tagName = 'doctrine_mongodb.odm.event_listener';
break;
}
switch ($typeConfig['driver']) {
case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
if (null !== $tagName) {
foreach ($this->getDoctrineEvents($typeConfig) as $event) {
$listenerDef->addTag($tagName, array('event' => $event));
}
}
$container->setDefinition($listenerId, $listenerDef);
@ -513,7 +527,6 @@ class FOSElasticaExtension extends Extension
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
break;
}
$events = array();

View file

@ -2,20 +2,22 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\Persistence\ManagerRegistry;
use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer as BaseTransformer;
use FOS\ElasticaBundle\Transformer\HighlightableModelInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
* elastica documents ids and doctrine object ids.
*/
abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
abstract class AbstractElasticaToModelTransformer extends BaseTransformer
{
/**
* Manager registry.
*
* @var ManagerRegistry
*/
protected $registry = null;
@ -38,21 +40,14 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
'query_builder_method' => 'createQueryBuilder',
);
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Instantiates a new Mapper.
*
* @param object $registry
* @param ManagerRegistry $registry
* @param string $objectClass
* @param array $options
*/
public function __construct($registry, $objectClass, array $options = array())
public function __construct(ManagerRegistry $registry, $objectClass, array $options = array())
{
$this->registry = $registry;
$this->objectClass = $objectClass;
@ -69,16 +64,6 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
return $this->objectClass;
}
/**
* Set the PropertyAccessor.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Transforms an array of elastica objects into an array of
* model objects fetched from the doctrine repository.
@ -111,10 +96,7 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
// sort objects in the order of ids
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor;
usort($objects, function ($a, $b) use ($idPos, $identifier, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
});
usort($objects, $this->getSortingClosure($idPos, $identifier));
return $objects;
}
@ -138,7 +120,7 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getIdentifierField()
{

View file

@ -7,9 +7,18 @@ 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;
/**
@ -18,94 +27,22 @@ abstract class AbstractProvider extends BaseAbstractProvider
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
* @param array $options
* @param array $baseOptions
* @param ManagerRegistry $managerRegistry
* @param SliceFetcherInterface $sliceFetcher
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
array $options,
ManagerRegistry $managerRegistry
array $baseOptions,
ManagerRegistry $managerRegistry,
SliceFetcherInterface $sliceFetcher = null
) {
parent::__construct($objectPersister, $indexable, $objectClass, array_merge(array(
'clear_object_manager' => true,
'debug_logging' => false,
'ignore_errors' => false,
'query_builder_method' => 'createQueryBuilder',
), $options));
parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions);
$this->managerRegistry = $managerRegistry;
}
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
if (!$this->options['debug_logging']) {
$logger = $this->disableLogging();
}
$queryBuilder = $this->createQueryBuilder();
$nbObjects = $this->countObjects($queryBuilder);
$offset = isset($options['offset']) ? intval($options['offset']) : 0;
$sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
$ignoreErrors = isset($options['ignore-errors']) ? $options['ignore-errors'] : $this->options['ignore_errors'];
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
if ($loggerClosure) {
$stepNbObjects = count($objects);
}
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
if (!$objects) {
if ($loggerClosure) {
$loggerClosure('<info>Entire batch was filtered away, skipping...</info>');
}
if ($this->options['clear_object_manager']) {
$manager->clear();
}
continue;
}
if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
} else {
try {
$this->objectPersister->insertMany($objects);
} catch (BulkResponseException $e) {
if ($loggerClosure) {
$loggerClosure(sprintf('<error>%s</error>', $e->getMessage()));
}
}
}
if ($this->options['clear_object_manager']) {
$manager->clear();
}
usleep($sleep);
if ($loggerClosure) {
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$timeDifference = microtime(true) - $stepStartTime;
$objectsPerSecond = $timeDifference ? ($stepNbObjects / $timeDifference) : $stepNbObjects;
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
}
}
if (!$this->options['debug_logging']) {
$this->enableLogging($logger);
}
$this->sliceFetcher = $sliceFetcher;
}
/**
@ -118,20 +55,13 @@ abstract class AbstractProvider extends BaseAbstractProvider
abstract protected function countObjects($queryBuilder);
/**
* Disables logging and returns the logger that was previously set.
* Creates the query builder, which will be used to fetch objects to index.
*
* @return mixed
* @param string $method
*
* @return object
*/
abstract protected function disableLogging();
/**
* Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
*
* @return mixed
*/
abstract protected function enableLogging($logger);
abstract protected function createQueryBuilder($method);
/**
* Fetches a slice of objects using the query builder.
@ -145,9 +75,96 @@ abstract class AbstractProvider extends BaseAbstractProvider
abstract protected function fetchSlice($queryBuilder, $limit, $offset);
/**
* Creates the query builder, which will be used to fetch objects to index.
*
* @return object
* {@inheritDoc}
*/
abstract protected function createQueryBuilder();
protected function doPopulate($options, \Closure $loggerClosure = null)
{
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
$queryBuilder = $this->createQueryBuilder($options['query_builder_method']);
$nbObjects = $this->countObjects($queryBuilder);
$offset = $options['offset'];
$objects = array();
for (; $offset < $nbObjects; $offset += $options['batch_size']) {
try {
$objects = $this->getSlice($queryBuilder, $options['batch_size'], $offset, $objects);
$objects = $this->filterObjects($options, $objects);
if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
}
} catch (BulkResponseException $e) {
if (!$options['ignore_errors']) {
throw $e;
}
if (null !== $loggerClosure) {
$loggerClosure(
$options['batch_size'],
$nbObjects,
sprintf('<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.
*
* @param $queryBuilder
* @param int $limit
* @param int $offset
* @param array $lastSlice
*
* @return array
*/
private function getSlice($queryBuilder, $limit, $offset, $lastSlice)
{
if (!$this->sliceFetcher) {
return $this->fetchSlice($queryBuilder, $limit, $offset);
}
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
$identifierFieldNames = $manager
->getClassMetadata($this->objectClass)
->getIdentifierFieldNames();
return $this->sliceFetcher->fetch(
$queryBuilder,
$limit,
$offset,
$lastSlice,
$identifierFieldNames
);
}
}

View file

@ -2,11 +2,11 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\IndexableInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@ -14,37 +14,40 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
* Automatically update ElasticSearch based on changes to the Doctrine source
* data. One listener is generated for each Doctrine entity / ElasticSearch type.
*/
class Listener implements EventSubscriber
class Listener
{
/**
* Object persister.
*
* @var ObjectPersister
* @var ObjectPersisterInterface
*/
protected $objectPersister;
/**
* List of subscribed events.
*
* @var array
*/
protected $events;
/**
* Configuration for the listener.
*
* @var string
* @var array
*/
private $config;
/**
* Objects scheduled for insertion and replacement.
* Objects scheduled for insertion.
*
* @var array
*/
public $scheduledForInsertion = array();
/**
* Objects scheduled to be updated or removed.
*
* @var array
*/
public $scheduledForUpdate = array();
/**
* IDs of objects scheduled for removal.
*
* @var array
*/
public $scheduledForDeletion = array();
@ -56,7 +59,7 @@ class Listener implements EventSubscriber
protected $propertyAccessor;
/**
* @var \FOS\ElasticaBundle\Provider\IndexableInterface
* @var IndexableInterface
*/
private $indexable;
@ -64,73 +67,50 @@ class Listener implements EventSubscriber
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param array $events
* @param IndexableInterface $indexable
* @param array $config
* @param null $logger
* @param LoggerInterface $logger
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
array $events,
IndexableInterface $indexable,
array $config = array(),
$logger = null
LoggerInterface $logger = null
) {
$this->config = array_merge(array(
'identifier' => 'id',
), $config);
$this->events = $events;
$this->indexable = $indexable;
$this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($logger) {
if ($logger && $this->objectPersister instanceof ObjectPersister) {
$this->objectPersister->setLogger($logger);
}
}
/**
* @see Doctrine\Common\EventSubscriber::getSubscribedEvents()
* Looks for new objects that should be indexed.
*
* @param LifecycleEventArgs $eventArgs
*/
public function getSubscribedEvents()
public function postPersist(LifecycleEventArgs $eventArgs)
{
return $this->events;
}
/**
* Provides unified method for retrieving a doctrine object from an EventArgs instance.
*
* @param EventArgs $eventArgs
*
* @return object Entity | Document
*
* @throws \RuntimeException if no valid getter is found.
*/
private function getDoctrineObject(EventArgs $eventArgs)
{
if (method_exists($eventArgs, 'getObject')) {
return $eventArgs->getObject();
} elseif (method_exists($eventArgs, 'getEntity')) {
return $eventArgs->getEntity();
} elseif (method_exists($eventArgs, 'getDocument')) {
return $eventArgs->getDocument();
}
throw new \RuntimeException('Unable to retrieve object from EventArgs.');
}
public function postPersist(EventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) {
$this->scheduledForInsertion[] = $entity;
}
}
public function postUpdate(EventArgs $eventArgs)
/**
* Looks for objects being updated that should be indexed or removed from the index.
*
* @param LifecycleEventArgs $eventArgs
*/
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
@ -145,10 +125,12 @@ class Listener implements EventSubscriber
/**
* Delete objects preRemove instead of postRemove so that we have access to the id. Because this is called
* preRemove, first check that the entity is managed by Doctrine.
*
* @param LifecycleEventArgs $eventArgs
*/
public function preRemove(EventArgs $eventArgs)
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
$entity = $eventArgs->getObject();
if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
@ -181,6 +163,10 @@ class Listener implements EventSubscriber
* 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()
{
@ -201,7 +187,7 @@ class Listener implements EventSubscriber
*
* @param object $object
*/
protected function scheduleForDeletion($object)
private function scheduleForDeletion($object)
{
if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
$this->scheduledForDeletion[] = $identifierValue;

View file

@ -44,7 +44,7 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
* {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@ -58,7 +58,7 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
* {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@ -67,21 +67,21 @@ class Provider extends AbstractProvider
}
return $queryBuilder
->limit($limit)
->skip($offset)
->limit($limit)
->getQuery()
->execute()
->toArray();
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
* {@inheritDoc}
*/
protected function createQueryBuilder()
protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
->{$this->options['query_builder_method']}();
->{$method}();
}
}

View file

@ -0,0 +1,44 @@
<?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

@ -46,7 +46,7 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
* {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@ -69,7 +69,9 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
* This method should remain in sync with SliceFetcher::fetch until it is deprecated and removed.
*
* {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@ -78,7 +80,7 @@ class Provider extends AbstractProvider
}
/*
* An orderBy DQL part is required to avoid feching the same row twice.
* An orderBy DQL part is required to avoid fetching the same row twice.
* @see http://stackoverflow.com/questions/6314879/does-limit-offset-length-require-order-by-for-pagination
* @see http://www.postgresql.org/docs/current/static/queries-limit.html
* @see http://www.sqlite.org/lang_select.html#orderby
@ -103,14 +105,14 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::createQueryBuilder()
* {@inheritDoc}
*/
protected function createQueryBuilder()
protected function createQueryBuilder($method)
{
return $this->managerRegistry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
// ORM query builders require an alias argument
->{$this->options['query_builder_method']}(static::ENTITY_ALIAS);
->{$method}(static::ENTITY_ALIAS);
}
}

View file

@ -0,0 +1,50 @@
<?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

@ -0,0 +1,24 @@
<?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);
}

38
Event/IndexEvent.php Normal file
View file

@ -0,0 +1,38 @@
<?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

@ -0,0 +1,70 @@
<?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;
}
}

70
Event/IndexResetEvent.php Normal file
View file

@ -0,0 +1,70 @@
<?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;
}
}

78
Event/TransformEvent.php Normal file
View file

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

View file

@ -0,0 +1,51 @@
<?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;
}
}

49
Event/TypeResetEvent.php Normal file
View file

@ -0,0 +1,49 @@
<?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

@ -6,6 +6,6 @@ class AliasIsIndexException extends \Exception
{
public function __construct($indexName)
{
parent::__construct(sprintf('Expected alias %s instead of index', $indexName));
parent::__construct(sprintf('Expected %s to be an alias but it is an index.', $indexName));
}
}

View file

@ -28,7 +28,12 @@ class AliasProcessor
*/
public function setRootName(IndexConfig $indexConfig, Index $index)
{
$index->overrideName(sprintf('%s_%s', $indexConfig->getElasticSearchName(), uniqid()));
$index->overrideName(
sprintf('%s_%s',
$indexConfig->getElasticSearchName(),
date('Y-m-d-His')
)
);
}
/**
@ -49,37 +54,47 @@ class AliasProcessor
$client = $index->getClient();
$aliasName = $indexConfig->getElasticSearchName();
$oldIndexName = false;
$oldIndexName = null;
$newIndexName = $index->getName();
try {
$aliasedIndexes = $this->getAliasedIndexes($client, $aliasName);
$oldIndexName = $this->getAliasedIndex($client, $aliasName);
} catch (AliasIsIndexException $e) {
if (!$force) {
throw $e;
}
$this->deleteIndex($client, $aliasName);
$aliasedIndexes = array();
}
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)
)
);
try {
$aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
$client->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $e) {
$this->cleanupRenameFailure($client, $newIndexName, $e);
}
// Delete the old index after the alias has been switched
if (null !== $oldIndexName) {
$this->deleteIndex($client, $oldIndexName);
}
}
/**
* Builds an ElasticSearch request to rename or create an alias.
*
* @param string|null $aliasedIndex
* @param string $aliasName
* @param string $newIndexName
* @return array
*/
private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
{
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) === 1) {
if (null !== $aliasedIndex) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName),
'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
);
}
@ -88,58 +103,68 @@ class AliasProcessor
'add' => array('index' => $newIndexName, 'alias' => $aliasName),
);
try {
$client->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$index->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getMessage()
);
}
return $aliasUpdateRequest;
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
), 0, $renameAliasException
/**
* Cleans up an index when we encounter a failure to rename the alias.
*
* @param Client $client
* @param string $indexName
* @param \Exception $renameAliasException
*/
private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
{
$additionalError = '';
try {
$this->deleteIndex($client, $indexName);
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$indexName,
$deleteNewIndexException->getMessage()
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($client, $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
), 0, $deleteOldIndexException
);
}
throw new \RuntimeException(sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
), 0, $renameAliasException);
}
/**
* Delete an index.
*
* @param Client $client
* @param string $indexName Index name to delete
*/
private function deleteIndex(Client $client, $indexName)
{
try {
$path = sprintf("%s", $indexName);
$client->request($path, Request::DELETE);
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(sprintf(
'Failed to delete index %s with message: %s',
$indexName,
$deleteOldIndexException->getMessage()
), 0, $deleteOldIndexException);
}
}
/**
* Returns array of indexes which are mapped to given alias.
* Returns the name of a single index that an alias points to or throws
* an exception if there is more than one.
*
* @param Client $client
* @param string $aliasName Alias name
*
* @return array
* @return string|null
*
* @throws AliasIsIndexException
*/
private function getAliasedIndexes(Client $client, $aliasName)
private function getAliasedIndex(Client $client, $aliasName)
{
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
@ -158,18 +183,15 @@ class AliasProcessor
}
}
return $aliasedIndexes;
}
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(sprintf(
'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
'either not used or is assigned to one index only',
$aliasName,
implode(', ', $aliasedIndexes)
));
}
/**
* Delete an index.
*
* @param Client $client
* @param string $indexName Index name to delete
*/
private function deleteIndex(Client $client, $indexName)
{
$path = sprintf("%s", $indexName);
$client->request($path, Request::DELETE);
return array_shift($aliasedIndexes);
}
}

View file

@ -6,6 +6,11 @@ use FOS\ElasticaBundle\Elastica\Index;
class IndexManager
{
/**
* @var Index
*/
private $defaultIndex;
/**
* @var array
*/

View file

@ -38,13 +38,13 @@ class MappingBuilder
}
$mapping = array();
if ($typeMappings) {
if (!empty($typeMappings)) {
$mapping['mappings'] = $typeMappings;
}
// 'warmers' => $indexConfig->getWarmers(),
$settings = $indexConfig->getSettings();
if ($settings) {
if (!empty($settings)) {
$mapping['settings'] = $settings;
}
@ -60,13 +60,19 @@ class MappingBuilder
*/
public function buildTypeMapping(TypeConfig $typeConfig)
{
$mapping = array_merge($typeConfig->getMapping(), array(
// 'date_detection' => true,
// 'dynamic_date_formats' => array()
// 'dynamic_templates' => $typeConfig->getDynamicTemplates(),
// 'numeric_detection' => false,
// 'properties' => array(),
));
$mapping = $typeConfig->getMapping();
if (null !== $typeConfig->getDynamicDateFormats()) {
$mapping['dynamic_date_formats'] = $typeConfig->getDynamicDateFormats();
}
if (null !== $typeConfig->getDateDetection()) {
$mapping['date_detection'] = $typeConfig->getDateDetection();
}
if (null !== $typeConfig->getNumericDetection()) {
$mapping['numeric_detection'] = $typeConfig->getNumericDetection();
}
if ($typeConfig->getIndexAnalyzer()) {
$mapping['index_analyzer'] = $typeConfig->getIndexAnalyzer();
@ -89,7 +95,7 @@ class MappingBuilder
$mapping['_meta']['model'] = $typeConfig->getModel();
}
if (!$mapping) {
if (empty($mapping)) {
// Empty mapping, we want it encoded as a {} instead of a []
$mapping = new \stdClass();
}
@ -106,6 +112,8 @@ class MappingBuilder
private function fixProperties(&$properties)
{
foreach ($properties as $name => &$property) {
unset($property['property_path']);
if (!isset($property['type'])) {
$property['type'] = 'string';
}

View file

@ -6,6 +6,9 @@ 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.
@ -18,10 +21,15 @@ class Resetter
private $aliasProcessor;
/***
* @var \FOS\ElasticaBundle\Configuration\Manager
* @var ConfigManager
*/
private $configManager;
/**
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* @var IndexManager
*/
@ -32,16 +40,32 @@ class Resetter
*/
private $mappingBuilder;
public function __construct(ConfigManager $configManager, IndexManager $indexManager, AliasProcessor $aliasProcessor, MappingBuilder $mappingBuilder)
{
/**
* @param ConfigManager $configManager
* @param IndexManager $indexManager
* @param AliasProcessor $aliasProcessor
* @param MappingBuilder $mappingBuilder
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
ConfigManager $configManager,
IndexManager $indexManager,
AliasProcessor $aliasProcessor,
MappingBuilder $mappingBuilder,
EventDispatcherInterface $eventDispatcher
) {
$this->aliasProcessor = $aliasProcessor;
$this->configManager = $configManager;
$this->dispatcher = $eventDispatcher;
$this->indexManager = $indexManager;
$this->mappingBuilder = $mappingBuilder;
}
/**
* Deletes and recreates all indexes.
*
* @param bool $populating
* @param bool $force
*/
public function resetAllIndexes($populating = false, $force = false)
{
@ -65,6 +89,9 @@ class Resetter
$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);
}
@ -75,6 +102,8 @@ class Resetter
if (!$populating and $indexConfig->isUseAlias()) {
$this->aliasProcessor->switchIndexAlias($indexConfig, $index, $force);
}
$this->dispatcher->dispatch(IndexResetEvent::POST_INDEX_RESET, $event);
}
/**
@ -91,6 +120,9 @@ class Resetter
$typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName);
$type = $this->indexManager->getIndex($indexName)->getType($typeName);
$event = new TypeResetEvent($indexName, $typeName);
$this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
try {
$type->delete();
} catch (ResponseException $e) {
@ -105,6 +137,8 @@ class Resetter
}
$type->setMapping($mapping);
$this->dispatcher->dispatch(TypeResetEvent::POST_TYPE_RESET, $event);
}
/**

View file

@ -20,8 +20,6 @@ class FantaPaginatorAdapter implements AdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
public function getNbResults()
{
@ -32,14 +30,24 @@ class FantaPaginatorAdapter implements AdapterInterface
* Returns Facets.
*
* @return mixed
*
* @api
*/
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.
*
@ -47,8 +55,6 @@ class FantaPaginatorAdapter implements AdapterInterface
* @param integer $length The length.
*
* @return array|\Traversable The slice.
*
* @api
*/
public function getSlice($offset, $length)
{

View file

@ -8,8 +8,6 @@ interface PaginatorAdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
public function getTotalHits();
@ -20,8 +18,6 @@ interface PaginatorAdapterInterface
* @param integer $length The length.
*
* @return PartialResultsInterface
*
* @api
*/
public function getResults($offset, $length);
@ -31,4 +27,11 @@ interface PaginatorAdapterInterface
* @return mixed
*/
public function getFacets();
/**
* Returns Aggregations.
*
* @return mixed
*/
public function getAggregations();
}

View file

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

View file

@ -37,6 +37,11 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*/
private $facets;
/**
* @var array for the aggregations
*/
private $aggregations;
/**
* @see PaginatorAdapterInterface::__construct
*
@ -69,7 +74,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
? (integer) $this->query->getParam('size')
: null;
if ($size && $size < $offset + $itemCountPerPage) {
if (null !== $size && $size < $offset + $itemCountPerPage) {
$itemCountPerPage = $size - $offset;
}
@ -84,6 +89,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
$this->aggregations = $resultSet->getAggregations();
return $resultSet;
}
@ -104,15 +110,21 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the number of results.
*
* If genuineTotal is provided as true, total hits is returned from the
* hits.total value from the search results instead of just returning
* the requested size.
*
* @param boolean $genuineTotal
*
* @return integer The number of results.
*/
public function getTotalHits()
public function getTotalHits($genuineTotal = false)
{
if (! isset($this->totalHits)) {
$this->totalHits = $this->searchable->search($this->query)->getTotalHits();
$this->totalHits = $this->searchable->count($this->query);
}
return $this->query->hasParam('size')
return $this->query->hasParam('size') && !$genuineTotal
? min($this->totalHits, (integer) $this->query->getParam('size'))
: $this->totalHits;
}
@ -131,6 +143,20 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
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.
*

View file

@ -49,4 +49,16 @@ class RawPartialResults implements PartialResultsInterface
return;
}
/**
* {@inheritDoc}
*/
public function getAggregations()
{
if ($this->resultSet->hasAggregations()) {
return $this->resultSet->getAggregations();
}
return;
}
}

View file

@ -42,6 +42,9 @@ class ObjectPersister implements ObjectPersisterInterface
return $object instanceof $this->objectClass;
}
/**
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;

View file

@ -10,6 +10,15 @@ namespace FOS\ElasticaBundle\Persister;
*/
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.

View file

@ -18,12 +18,16 @@ class ObjectSerializerPersister extends ObjectPersister
protected $serializer;
/**
* @param Type $type
* @param ModelToElasticaTransformerInterface $transformer
* @param string $objectClass
* @param callable $serializer
*/
public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer)
{
parent::__construct($type, $transformer, $objectClass, array());
$this->serializer = $serializer;
$this->serializer = $serializer;
}
/**

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@ -14,7 +15,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
*
* @author William Durand <william.durand1@gmail.com>
*/
class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
/**
* Propel model class to map to Elastica documents.
@ -33,13 +34,6 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
'identifier' => 'id',
);
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Constructor.
*
@ -52,16 +46,6 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$this->options = array_merge($this->options, $options);
}
/**
* Set the PropertyAccessor instance.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Transforms an array of Elastica document into an array of Propel entities
* fetched from the database.
@ -82,11 +66,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
// Sort objects in the order of their IDs
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor;
$sortCallback = function ($a, $b) use ($idPos, $identifier, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
};
$sortCallback = $this->getSortingClosure($idPos, $identifier);
if (is_object($objects)) {
$objects->uasort($sortCallback);
@ -105,7 +85,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$objects = $this->transform($elasticaObjects);
$result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) {
for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
}

View file

@ -12,46 +12,62 @@ use FOS\ElasticaBundle\Provider\AbstractProvider;
class Provider extends AbstractProvider
{
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
* {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
public function doPopulate($options, \Closure $loggerClosure = null)
{
$queryClass = $this->objectClass.'Query';
$nbObjects = $queryClass::create()->count();
$offset = isset($options['offset']) ? intval($options['offset']) : 0;
$sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$offset = $options['offset'];
for (; $offset < $nbObjects; $offset += $options['batch_size']) {
$objects = $queryClass::create()
->limit($batchSize)
->limit($options['batch_size'])
->offset($offset)
->find()
->getArrayCopy();
if ($loggerClosure) {
$stepNbObjects = count($objects);
}
$objects = array_filter($objects, array($this, 'isObjectIndexable'));
if (!$objects) {
$loggerClosure('<info>Entire batch was filtered away, skipping...</info>');
continue;
$objects = $this->filterObjects($options, $objects);
if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
}
$this->objectPersister->insertMany($objects);
usleep($sleep);
usleep($options['sleep']);
if ($loggerClosure) {
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
$loggerClosure($options['batch_size'], $nbObjects);
}
}
}
/**
* {@inheritDoc}
*/
protected function configureOptions()
{
parent::configureOptions();
$this->resolver->setDefaults(array(
'clear_object_manager' => true,
'debug_logging' => false,
'ignore_errors' => false,
'offset' => 0,
'query_builder_method' => null,
'sleep' => 0
));
}
/**
* {@inheritDoc}
*/
protected function disableLogging()
{
}
/**
* {@inheritDoc}
*/
protected function enableLogging($logger)
{
}
}

View file

@ -3,6 +3,7 @@
namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* AbstractProvider.
@ -10,9 +11,9 @@ use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
abstract class AbstractProvider implements ProviderInterface
{
/**
* @var ObjectPersisterInterface
* @var array
*/
protected $objectPersister;
protected $baseOptions;
/**
* @var string
@ -20,12 +21,17 @@ abstract class AbstractProvider implements ProviderInterface
protected $objectClass;
/**
* @var array
* @var ObjectPersisterInterface
*/
protected $options;
protected $objectPersister;
/**
* @var Indexable
* @var OptionsResolver
*/
protected $resolver;
/**
* @var IndexableInterface
*/
private $indexable;
@ -35,26 +41,117 @@ abstract class AbstractProvider implements ProviderInterface
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
* @param array $options
* @param array $baseOptions
*/
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
array $options = array()
array $baseOptions = array()
) {
$this->baseOptions = $baseOptions;
$this->indexable = $indexable;
$this->objectClass = $objectClass;
$this->objectPersister = $objectPersister;
$this->resolver = new OptionsResolver();
$this->configureOptions();
}
$this->options = array_merge(array(
/**
* {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
$options = $this->resolveOptions($options);
$logger = !$options['debug_logging'] ?
$this->disableLogging() :
null;
$this->doPopulate($options, $loggerClosure);
if (null !== $logger) {
$this->enableLogging($logger);
}
}
/**
* Disables logging and returns the logger that was previously set.
*
* @return mixed
*/
abstract protected function disableLogging();
/**
* Perform actual population.
*
* @param array $options
* @param \Closure $loggerClosure
*/
abstract protected function doPopulate($options, \Closure $loggerClosure = null);
/**
* Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
*
* @return mixed
*/
abstract protected function enableLogging($logger);
/**
* Configures the option resolver.
*/
protected function configureOptions()
{
$this->resolver->setDefaults(array(
'batch_size' => 100,
), $options);
'skip_indexable_check' => false,
));
$this->resolver->setAllowedTypes(array(
'batch_size' => 'int'
));
$this->resolver->setRequired(array(
'indexName',
'typeName',
));
}
/**
* Filters objects away if they are not indexable.
*
* @param array $options
* @param array $objects
* @return array
*/
protected function filterObjects(array $options, array $objects)
{
if ($options['skip_indexable_check']) {
return $objects;
}
$index = $options['indexName'];
$type = $options['typeName'];
$return = array();
foreach ($objects as $object) {
if (!$this->indexable->isObjectIndexable($index, $type, $object)) {
continue;
}
$return[] = $object;
}
return $return;
}
/**
* Checks if a given object should be indexed or not.
*
* @deprecated To be removed in 4.0
*
* @param object $object
*
* @return bool
@ -62,8 +159,8 @@ abstract class AbstractProvider implements ProviderInterface
protected function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
$this->options['indexName'],
$this->options['typeName'],
$this->baseOptions['indexName'],
$this->baseOptions['typeName'],
$object
);
}
@ -71,6 +168,8 @@ abstract class AbstractProvider implements ProviderInterface
/**
* Get string with RAM usage information (current and peak).
*
* @deprecated To be removed in 4.0
*
* @return string
*/
protected function getMemoryUsage()
@ -80,4 +179,17 @@ abstract class AbstractProvider implements ProviderInterface
return sprintf('(RAM : current=%uMo peak=%uMo)', $memory, $memoryMax);
}
/**
* Merges the base options provided by the class with options passed to the populate
* method and runs them through the resolver.
*
* @param array $options
*
* @return array
*/
protected function resolveOptions(array $options)
{
return $this->resolver->resolve(array_merge($this->baseOptions, $options));
}
}

View file

@ -55,6 +55,7 @@ class Indexable implements IndexableInterface
/**
* @param array $callbacks
* @param ContainerInterface $container
*/
public function __construct(array $callbacks, ContainerInterface $container)
{
@ -81,7 +82,7 @@ class Indexable implements IndexableInterface
}
if ($callback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($callback, array(
return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
$this->getExpressionVar($object) => $object,
));
@ -112,39 +113,48 @@ class Indexable implements IndexableInterface
return $callback;
}
if (is_array($callback)) {
list($class, $method) = $callback + array(null, null);
if (is_object($class)) {
$class = get_class($class);
}
if (strpos($class, '@') === 0) {
$service = $this->container->get(substr($class, 1));
return array($service, $method);
}
if ($class && $method) {
throw new \InvalidArgumentException(sprintf('Callback for type "%s", "%s::%s()", is not callable.', $type, $class, $method));
}
if (is_array($callback) && !is_object($callback[0])) {
return $this->processArrayToCallback($type, $callback);
}
if (is_string($callback) && $expression = $this->getExpressionLanguage()) {
$callback = new Expression($callback);
try {
$expression->compile($callback, array('object', $this->getExpressionVar($object)));
return $callback;
} catch (SyntaxError $e) {
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is an invalid expression', $type), $e->getCode(), $e);
}
if (is_string($callback)) {
return $this->buildExpressionCallback($type, $object, $callback);
}
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
}
/**
* Processes a string expression into an Expression.
*
* @param string $type
* @param mixed $object
* @param string $callback
*
* @return Expression
*/
private function buildExpressionCallback($type, $object, $callback)
{
$expression = $this->getExpressionLanguage();
if (!$expression) {
throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
}
try {
$callback = new Expression($callback);
$expression->compile($callback, array(
'object', $this->getExpressionVar($object)
));
return $callback;
} catch (SyntaxError $e) {
throw new \InvalidArgumentException(sprintf(
'Callback for type "%s" is an invalid expression',
$type
), $e->getCode(), $e);
}
}
/**
* Retreives a cached callback, or creates a new callback if one is not found.
*
@ -163,15 +173,13 @@ class Indexable implements IndexableInterface
}
/**
* @return bool|ExpressionLanguage
* Returns the ExpressionLanguage class if it is available.
*
* @return ExpressionLanguage|null
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$this->expressionLanguage = new ExpressionLanguage();
}
@ -179,14 +187,54 @@ class Indexable implements IndexableInterface
}
/**
* Returns the variable name to be used to access the object when using the ExpressionLanguage
* component to parse and evaluate an expression.
*
* @param mixed $object
*
* @return string
*/
private function getExpressionVar($object = null)
{
if (!is_object($object)) {
return 'object';
}
$ref = new \ReflectionClass($object);
return strtolower($ref->getShortName());
}
/**
* Processes an array into a callback. Replaces the first element with a service if
* it begins with an @.
*
* @param string $type
* @param array $callback
* @return array
*/
private function processArrayToCallback($type, array $callback)
{
list($class, $method) = $callback + array(null, '__invoke');
if (strpos($class, '@') === 0) {
$service = $this->container->get(substr($class, 1));
$callback = array($service, $method);
if (!is_callable($callback)) {
throw new \InvalidArgumentException(sprintf(
'Method "%s" on service "%s" is not callable.',
$method,
substr($class, 1)
));
}
return $callback;
}
throw new \InvalidArgumentException(sprintf(
'Unable to parse callback array for type "%s"',
$type
));
}
}

View file

@ -12,6 +12,11 @@ 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
*

View file

@ -59,7 +59,7 @@ class ProviderRegistry implements ContainerAwareInterface
*
* @param string $index
*
* @return array of ProviderInterface instances
* @return ProviderInterface[]
*
* @throws \InvalidArgumentException if no providers were registered for the index
*/

View file

@ -11,15 +11,16 @@ Symfony2. Features include:
> **Note** Propel support is limited and contributions fixing issues are welcome!
[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Unstable Version](https://poser.pugx.org/friendsofsymfony/elastica-bundle/v/unstable.svg)](https://packagist.org/packages/friendsofsymfony/elastica-bundle)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSElasticaBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSElasticaBundle/?branch=master)
Documentation
-------------
Documentation for FOSElasticaBundle is in `Resources/doc/index.md`
[Read the documentation for 3.0.x (master)](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
[Read the documentation for 3.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/master/Resources/doc/index.md)
[Read the documentation for 2.1.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/2.1.x/README.md)
[Read the documentation for 3.0.x](https://github.com/FriendsOfSymfony/FOSElasticaBundle/blob/3.0.x/Resources/doc/index.md)
Installation
------------

View file

@ -41,6 +41,7 @@
<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. -->

View file

@ -5,25 +5,28 @@
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">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine_mongodb" />
<argument type="service" id="doctrine_mongodb" /> <!-- manager registry -->
<argument type="service" id="fos_elastica.slice_fetcher.mongodb" /> <!-- slice fetcher -->
</service>
<service id="fos_elastica.listener.prototype.mongodb" class="%fos_elastica.listener.prototype.mongodb.class%" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->

View file

@ -4,25 +4,29 @@
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.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>
<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>
<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">
<argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="doctrine" />
<argument type="service" id="doctrine" /> <!-- manager registry -->
<argument type="service" id="fos_elastica.slice_fetcher.orm" /> <!-- slice fetcher -->
</service>
<service id="fos_elastica.listener.prototype.orm" class="%fos_elastica.listener.prototype.orm.class%" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->

View file

@ -12,7 +12,8 @@
<services>
<service id="fos_elastica.model_to_elastica_transformer" class="%fos_elastica.model_to_elastica_transformer.class%" public="false" abstract="true">
<argument type="collection" /> <!-- options -->
<argument type="collection" /> <!-- options -->
<argument type="service" id="event_dispatcher" /> <!-- options -->
<call method="setPropertyAccessor">
<argument type="service" id="fos_elastica.property_accessor" />
</call>

View file

@ -0,0 +1,33 @@
##### Custom Properties
Since FOSElasticaBundle 3.1.0, we now dispatch an event for each transformation of an
object into an Elastica document which allows you to set custom properties on the Elastica
document for indexing.
Set up an event listener or subscriber for
`FOS\ElasticaBundle\Event\TransformEvent::POST_TRANSFORM` to be able to inject your own
parameters.
```php
class CustomPropertyListener implements EventSubscriberInterface
{
private $anotherService;
// ...
public function addCustomProperty(TransformEvent $event)
{
$document = $event->getDocument();
$custom = $this->anotherService->calculateCustom($event->getObject());
$document->set('custom', $custom);
}
public static function getSubscribedEvents()
{
return array(
TransformEvent::POST_TRANSFORM => 'addCustomProperty',
);
}
}
```

View file

@ -4,7 +4,7 @@ As well as the default repository you can create a custom repository for an enti
methods for particular searches. These need to extend `FOS\ElasticaBundle\Repository` to have
access to the finder:
```
```php
<?php
namespace Acme\ElasticaBundle\SearchRepository;
@ -23,37 +23,41 @@ class UserRepository extends Repository
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
```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:
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
```php
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->findWithCustomQuery('bob');
/** var array of Acme\UserBundle\Entity\User */
$users = $repository->findWithCustomQuery('bob');
```
Alternatively you can specify the custom repository using an annotation in the entity:
```
```php
<?php
namespace Application\UserBundle\Entity;
@ -69,4 +73,4 @@ class User
//---
}
```
```

View file

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

View file

@ -1,40 +1,50 @@
Step 1: Setting up the bundle
=============================
A) Install FOSElasticaBundle
----------------------------
A: Download the Bundle
----------------------
FOSElasticaBundle is installed using [Composer](https://getcomposer.org).
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
```bash
$ php composer.phar require friendsofsymfony/elastica-bundle "~3.0"
$ composer require friendsofsymfony/elastica-bundle
```
This command requires you to have Composer installed globally, as explained
in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
of the Composer documentation.
### Elasticsearch
Instructions for installing and deploying Elasticsearch may be found
[here](http://www.elasticsearch.org/guide/reference/setup/installation/).
Instructions for installing and deploying Elasticsearch may be found [here](https://www.elastic.co/downloads/elasticsearch).
Step 2: Enable the Bundle
-------------------------
B) Enable FOSElasticaBundle
---------------------------
Enable FOSElasticaBundle in your AppKernel:
Then, enable the bundle by adding the following line in the `app/AppKernel.php`
file of your project:
```php
<?php
// app/AppKernel.php
public function registerBundles()
// ...
class AppKernel extends Kernel
{
$bundles = array(
public function registerBundles()
{
$bundles = array(
// ...
new FOS\ElasticaBundle\FOSElasticaBundle(),
);
// ...
new FOS\ElasticaBundle\FOSElasticaBundle(),
);
}
}
```
C) Basic Bundle Configuration
C: Basic Bundle Configuration
-----------------------------
The basic minimal configuration for FOSElasticaBundle is one client with one Elasticsearch
@ -71,7 +81,7 @@ In this case, the service `fos_elastica.index.app` will relate to an ElasticSear
that varies depending on your kernel's environment. For example, in dev it will relate to
`app_dev`.
D) Defining index types
D: Defining index types
-----------------------
By default, FOSElasticaBundle requires each type that is to be indexed to be mapped.
@ -125,7 +135,7 @@ Below is an example for the Doctrine ORM.
There are a significant number of options available for types, that can be
[found here](types.md)
E) Populating the Elasticsearch index
E: Populating the Elasticsearch index
-------------------------------------
When using the providers and listeners that come with the bundle, any new or modified
@ -140,7 +150,7 @@ $ php app/console fos:elastica:populate
The command will also create all indexes and types defined if they do not already exist
on the Elasticsearch server.
F) Usage
F: Usage
--------
Usage documentation for the bundle is available [here](usage.md)

View file

@ -1,6 +1,34 @@
Type configuration
==================
Custom Property Paths
---------------------
Since FOSElasticaBundle 3.1.0, it is now possible to define custom property paths
to be used for data retrieval from the underlying model.
```yaml
user:
mappings:
username:
property_path: indexableUsername
firstName:
property_path: names[first]
```
This feature uses the Symfony PropertyAccessor component and supports all features
that the component supports.
The above example would retrieve an indexed field `username` from the property
`User->indexableUsername`, and the indexed field `firstName` would be populated from a
key `first` from an array on `User->names`.
Setting the property path to `false` will disable transformation of that value. In this
case the mapping will be created but no value will be populated while indexing. You can
populate this value by listening to the `POST_TRANSFORM` event emitted by this bundle.
See [cookbook/custom-properties.md](cookbook/custom-properties.md) for more information
about this event.
Handling missing results with FOSElasticaBundle
-----------------------------------------------
@ -173,13 +201,18 @@ index enabled users.
The callback option supports multiple approaches:
* A method on the object itself provided as a string. `enabled` will call
`Object->enabled()`
`Object->enabled()`. Note that this does not support chaining methods with dot notation
like property paths. To achieve something similar use the ExpressionLanguage option
below.
* An array of a service id and a method which will be called with the object as the first
and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
method on a service defined as my_custom_service.
* An array of a class and a static method to call on that class which will be called with
the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
will call Acme\DemoBundle\IndexableChecker::isIndexable($object)
* A single element array with a service id can be used if the service has an __invoke
method. Such an invoke method must accept a single parameter for the object to be indexed.
`[ @my_custom_invokable_service ]`
* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
expression provided as a string. The object being indexed will be supplied as `object`
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more

View file

@ -26,7 +26,9 @@ $userPaginator = $finder->findPaginated('bob');
$countOfResults = $userPaginator->getNbResults();
// Option 3b. KnpPaginator resultset
$paginator = $this->get('knp_paginator');
$results = $finder->createPaginatorAdapter('bob');
$pagination = $paginator->paginate($results, $page, 10);
```
Faceted Searching
@ -160,7 +162,7 @@ fos_elastica:
site:
settings:
index:
analysis:
analysis:
analyzer:
my_analyzer:
type: snowball

View file

@ -8,7 +8,7 @@ use JMS\Serializer\SerializerInterface;
class Callback
{
protected $serializer;
protected $groups;
protected $groups = array();
protected $version;
public function setSerializer($serializer)
@ -23,10 +23,8 @@ class Callback
{
$this->groups = $groups;
if ($this->groups) {
if (!$this->serializer instanceof SerializerInterface) {
throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
}
if (!empty($this->groups) && !$this->serializer instanceof SerializerInterface) {
throw new \RuntimeException('Setting serialization groups requires using "JMS\Serializer\Serializer".');
}
}
@ -34,10 +32,8 @@ class Callback
{
$this->version = $version;
if ($this->version) {
if (!$this->serializer instanceof SerializerInterface) {
throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
}
if ($this->version && !$this->serializer instanceof SerializerInterface) {
throw new \RuntimeException('Setting serialization version requires using "JMS\Serializer\Serializer".');
}
}
@ -45,7 +41,7 @@ class Callback
{
$context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array();
if ($this->groups) {
if (!empty($this->groups)) {
$context->setGroups($this->groups);
}

View file

@ -32,6 +32,10 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
if (null != $facets) {
$event->setCustomPaginationParameter('facets', $facets);
}
$aggregations = $results->getAggregations();
if (null != $aggregations) {
$event->setCustomPaginationParameter('aggregations', $aggregations);
}
$event->stopPropagation();
}

View file

@ -9,8 +9,8 @@ use Symfony\Component\DependencyInjection\Container;
class ResetCommandTest extends \PHPUnit_Framework_TestCase
{
private $command;
private $resetter;
private $indexManager;
public function setup()

View file

@ -16,7 +16,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
$listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
@ -35,7 +35,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, false);
$listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
@ -55,7 +55,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
$listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
@ -89,7 +89,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
@ -124,7 +124,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, array(), $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->getId(), current($listener->scheduledForDeletion));
@ -157,7 +157,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, array(), $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
$listener = $this->createListener($persister, $indexable, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
$listener->preRemove($eventArgs);
$this->assertEquals($entity->identifier, current($listener->scheduledForDeletion));
@ -272,6 +272,7 @@ namespace FOS\ElasticaBundle\Tests\Doctrine\Listener;
class Entity
{
private $id;
public $identifier;
/**
* @param integer $id

View file

@ -13,6 +13,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
private $options;
private $managerRegistry;
private $indexable;
private $sliceFetcher;
public function setUp()
{
@ -28,6 +29,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('getManagerForClass')
->with($this->objectClass)
->will($this->returnValue($this->objectManager));
$this->sliceFetcher = $this->getMockSliceFetcher();
}
/**
@ -41,6 +44,53 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$queryBuilder = new \stdClass();
$provider->expects($this->once())
->method('createQueryBuilder')
->will($this->returnValue($queryBuilder));
$provider->expects($this->once())
->method('countObjects')
->with($queryBuilder)
->will($this->returnValue($nbObjects));
$this->indexable->expects($this->any())
->method('isObjectIndexable')
->with('index', 'type', $this->anything())
->will($this->returnValue(true));
$previousSlice = array();
foreach ($objectsByIteration as $i => $objects) {
$offset = $objects[0] - 1;
$this->sliceFetcher->expects($this->at($i))
->method('fetch')
->with($queryBuilder, $batchSize, $offset, $previousSlice, array('id'))
->will($this->returnValue($objects));
$this->objectManager->expects($this->at($i))
->method('clear');
$previousSlice = $objects;
}
$this->objectPersister->expects($this->exactly(count($objectsByIteration)))
->method('insertMany');
$provider->populate();
}
/**
* @dataProvider providePopulateIterations
*/
public function testPopulateIterationsWithoutSliceFetcher($nbObjects, $objectsByIteration, $batchSize)
{
$this->options['batch_size'] = $batchSize;
$provider = $this->getMockAbstractProvider(false);
$queryBuilder = new \stdClass();
$provider->expects($this->once())
->method('createQueryBuilder')
->will($this->returnValue($queryBuilder));
@ -103,8 +153,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
$this->sliceFetcher->expects($this->any())
->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@ -123,14 +173,14 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$nbObjects = 1;
$objects = array(1);
$provider = $this->getMockAbstractProvider();
$provider = $this->getMockAbstractProvider(true);
$provider->expects($this->any())
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
$this->sliceFetcher->expects($this->any())
->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@ -155,8 +205,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
$this->sliceFetcher->expects($this->any())
->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@ -187,8 +237,8 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
$this->sliceFetcher->expects($this->any())
->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->any())
@ -202,7 +252,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('Elastica\Exception\Bulk\ResponseException');
$provider->populate(null, array('ignore-errors' => false));
$provider->populate(null, array('ignore_errors' => false));
}
public function testPopulateRunsIndexCallable()
@ -214,8 +264,9 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$provider->expects($this->any())
->method('countObjects')
->will($this->returnValue($nbObjects));
$provider->expects($this->any())
->method('fetchSlice')
$this->sliceFetcher->expects($this->any())
->method('fetch')
->will($this->returnValue($objects));
$this->indexable->expects($this->at(0))
@ -229,15 +280,17 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->objectPersister->expects($this->once())
->method('insertMany')
->with(array(1 => 2));
->with(array(2));
$provider->populate();
}
/**
* @param boolean $setSliceFetcher Whether or not to set the slice fetcher.
*
* @return \FOS\ElasticaBundle\Doctrine\AbstractProvider|\PHPUnit_Framework_MockObject_MockObject
*/
private function getMockAbstractProvider()
private function getMockAbstractProvider($setSliceFetcher = true)
{
return $this->getMockForAbstractClass('FOS\ElasticaBundle\Doctrine\AbstractProvider', array(
$this->objectPersister,
@ -245,6 +298,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$this->objectClass,
$this->options,
$this->managerRegistry,
$setSliceFetcher ? $this->sliceFetcher : null
));
}
@ -271,7 +325,17 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
*/
private function getMockObjectManager()
{
return $this->getMock(__NAMESPACE__.'\ObjectManager');
$mock = $this->getMock(__NAMESPACE__.'\ObjectManager');
$mock->expects($this->any())
->method('getClassMetadata')
->will($this->returnSelf());
$mock->expects($this->any())
->method('getIdentifierFieldNames')
->will($this->returnValue(array('id')));
return $mock;
}
/**
@ -289,6 +353,14 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
return $this->getMock('FOS\ElasticaBundle\Provider\IndexableInterface');
}
/**
* @return \FOS\ElasticaBundle\Doctrine\SliceFetcherInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private function getMockSliceFetcher()
{
return $this->getMock('FOS\ElasticaBundle\Doctrine\SliceFetcherInterface');
}
}
/**
@ -298,4 +370,6 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
interface ObjectManager
{
public function clear();
public function getClassMetadata();
public function getIdentifierFieldNames();
}

View file

@ -53,6 +53,9 @@ class MappingToElasticaTest extends WebTestCase
$mapping = $type->getMapping();
$this->assertNotEmpty($mapping, 'Mapping was populated');
$this->assertFalse($mapping['type']['date_detection']);
$this->assertTrue($mapping['type']['numeric_detection']);
$this->assertEquals(array('yyyy-MM-dd'), $mapping['type']['dynamic_date_formats']);
$this->assertArrayHasKey('store', $mapping['type']['properties']['field1']);
$this->assertTrue($mapping['type']['properties']['field1']['store']);
$this->assertArrayNotHasKey('store', $mapping['type']['properties']['field2']);
@ -110,6 +113,7 @@ class MappingToElasticaTest extends WebTestCase
/**
* @param Client $client
* @param string $type
*
* @return \Elastica\Type
*/

View file

@ -0,0 +1,54 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace FOS\ElasticaBundle\Tests\Functional;
use Elastica\Query\Match;
/**
* @group functional
*/
class PropertyPathTest extends WebTestCase
{
public function testContainerSource()
{
$client = $this->createClient(array('test_case' => 'ORM'));
/** @var \FOS\ElasticaBundle\Persister\ObjectPersister $persister */
$persister = $client->getContainer()->get('fos_elastica.object_persister.index.property_paths_type');
$obj = new TypeObj();
$obj->coll = 'Hello';
$persister->insertOne($obj);
/** @var \Elastica\Index $elClient */
$index = $client->getContainer()->get('fos_elastica.index.index');
$index->flush(true);
$query = new Match();
$query->setField('something', 'Hello');
$search = $index->createSearch($query);
$this->assertEquals(1, $search->count());
}
protected function setUp()
{
parent::setUp();
$this->deleteTmpDir('Basic');
}
protected function tearDown()
{
parent::tearDown();
$this->deleteTmpDir('Basic');
}
}

View file

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

View file

@ -51,6 +51,8 @@ fos_elastica:
index_analyzer: my_analyzer
type:
search_analyzer: my_analyzer
date_detection: false
dynamic_date_formats: [ 'yyyy-MM-dd' ]
dynamic_templates:
- dates:
match: "date_*"
@ -61,6 +63,7 @@ fos_elastica:
mapping:
analyzer: english
type: string
numeric_detection: true
properties:
field1: ~
field2:

View file

@ -13,12 +13,12 @@ namespace FOS\ElasticaBundle\Tests\Functional\app\ORM;
class IndexableService
{
public function isIndexable($object)
public function isIndexable()
{
return true;
}
public static function isntIndexable($object)
public static function isntIndexable()
{
return false;
}

View file

@ -65,6 +65,18 @@ fos_elastica:
provider: ~
listener:
is_indexable_callback: [ 'FOS\ElasticaBundle\Tests\Functional\app\ORM\IndexableService', 'isntIndexable' ]
property_paths_type:
persistence:
driver: orm
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
provider: ~
properties:
field1:
property_path: field2
something:
property_path: coll
dynamic:
property_path: false
second_index:
index_name: foselastica_orm_test_second_%kernel.environment%
types:

View file

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

View file

@ -0,0 +1,223 @@
<?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\Tests\Index;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Index\AliasProcessor;
class AliasProcessorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var AliasProcessor
*/
private $processor;
/**
* @dataProvider getSetRootNameData
* @param string $name
* @param array $configArray
* @param string $resultStartsWith
*/
public function testSetRootName($name, $configArray, $resultStartsWith)
{
$indexConfig = new IndexConfig($name, array(), $configArray);
$index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
->disableOriginalConstructor()
->getMock();
$index->expects($this->once())
->method('overrideName')
->with($this->stringStartsWith($resultStartsWith));
$this->processor->setRootName($indexConfig, $index);
}
public function testSwitchAliasNoAliasSet()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array()));
$client->expects($this->at(1))
->method('request')
->with('_aliases', 'POST', array('actions' => array(
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
)));
$this->processor->switchIndexAlias($indexConfig, $index, false);
}
public function testSwitchAliasExistingAliasSet()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'old_unique_name' => array('aliases' => array('name'))
)));
$client->expects($this->at(1))
->method('request')
->with('_aliases', 'POST', array('actions' => array(
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
)));
$this->processor->switchIndexAlias($indexConfig, $index, false);
}
/**
* @expectedException \RuntimeException
*/
public function testSwitchAliasThrowsWhenMoreThanOneExists()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'old_unique_name' => array('aliases' => array('name')),
'another_old_unique_name' => array('aliases' => array('name'))
)));
$this->processor->switchIndexAlias($indexConfig, $index, false);
}
/**
* @expectedException \FOS\ElasticaBundle\Exception\AliasIsIndexException
*/
public function testSwitchAliasThrowsWhenAliasIsAnIndex()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'name' => array(),
)));
$this->processor->switchIndexAlias($indexConfig, $index, false);
}
public function testSwitchAliasDeletesIndexCollisionIfForced()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'name' => array(),
)));
$client->expects($this->at(1))
->method('request')
->with('name', 'DELETE');
$this->processor->switchIndexAlias($indexConfig, $index, true);
}
public function testSwitchAliasDeletesOldIndex()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'old_unique_name' => array('aliases' => array('name')),
)));
$client->expects($this->at(1))
->method('request')
->with('_aliases', 'POST', array('actions' => array(
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
)));
$client->expects($this->at(2))
->method('request')
->with('old_unique_name', 'DELETE');
$this->processor->switchIndexAlias($indexConfig, $index, true);
}
public function testSwitchAliasCleansUpOnRenameFailure()
{
$indexConfig = new IndexConfig('name', array(), array());
list($index, $client) = $this->getMockedIndex('unique_name');
$client->expects($this->at(0))
->method('request')
->with('_aliases', 'GET')
->willReturn(new Response(array(
'old_unique_name' => array('aliases' => array('name')),
)));
$client->expects($this->at(1))
->method('request')
->with('_aliases', 'POST', array('actions' => array(
array('remove' => array('index' => 'old_unique_name', 'alias' => 'name')),
array('add' => array('index' => 'unique_name', 'alias' => 'name'))
)))
->will($this->throwException(new ResponseException(new Request(''), new Response(''))));
$client->expects($this->at(2))
->method('request')
->with('unique_name', 'DELETE');
// Not an annotation: we do not want a RuntimeException until now.
$this->setExpectedException('RuntimeException');
$this->processor->switchIndexAlias($indexConfig, $index, true);
}
public function getSetRootNameData()
{
return array(
array('name', array(), 'name_'),
array('name', array('elasticSearchName' => 'notname'), 'notname_')
);
}
protected function setUp()
{
$this->processor = new AliasProcessor();
}
private function getMockedIndex($name)
{
$index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
->disableOriginalConstructor()
->getMock();
$client = $this->getMockBuilder('Elastica\\Client')
->disableOriginalConstructor()
->getMock();
$index->expects($this->any())
->method('getClient')
->willReturn($client);
$index->expects($this->any())
->method('getName')
->willReturn($name);
return array($index, $client);
}
}

View file

@ -5,8 +5,14 @@ namespace FOS\ElasticaBundle\Tests\Index;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
use Elastica\Type;
use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;
use FOS\ElasticaBundle\Elastica\Index;
use FOS\ElasticaBundle\Event\IndexResetEvent;
use FOS\ElasticaBundle\Event\TypeResetEvent;
use FOS\ElasticaBundle\Index\AliasProcessor;
use FOS\ElasticaBundle\Index\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase
@ -16,227 +22,253 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
*/
private $resetter;
private $configManager;
private $indexManager;
private $aliasProcessor;
private $configManager;
private $dispatcher;
private $elasticaClient;
private $indexManager;
private $mappingBuilder;
public function setUp()
public function testResetAllIndexes()
{
$this->markTestIncomplete('To be rewritten');
$indexName = 'index1';
$indexConfig = new IndexConfig($indexName, array(), array());
$this->mockIndex($indexName, $indexConfig);
$this->configManager->expects($this->once())
->method('getIndexNames')
->will($this->returnValue(array($indexName)));
$this->dispatcherExpects(array(
array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
));
$this->elasticaClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
array('index1/', 'DELETE'),
array('index1/', 'PUT', array(), array())
);
$this->resetter->resetAllIndexes();
}
public function testResetIndex()
{
$indexConfig = new IndexConfig('index1', array(), array());
$this->mockIndex('index1', $indexConfig);
$this->dispatcherExpects(array(
array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
));
$this->elasticaClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
array('index1/', 'DELETE'),
array('index1/', 'PUT', array(), array())
);
$this->resetter->resetIndex('index1');
}
public function testResetIndexWithDifferentName()
{
$indexConfig = new IndexConfig('index1', array(), array(
'elasticSearchName' => 'notIndex1'
));
$this->mockIndex('index1', $indexConfig);
$this->dispatcherExpects(array(
array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
));
$this->elasticaClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
array('index1/', 'DELETE'),
array('index1/', 'PUT', array(), array())
);
$this->resetter->resetIndex('index1');
}
public function testResetIndexWithDifferentNameAndAlias()
{
$indexConfig = new IndexConfig('index1', array(), array(
'elasticSearchName' => 'notIndex1',
'useAlias' => true
));
$index = $this->mockIndex('index1', $indexConfig);
$this->dispatcherExpects(array(
array(IndexResetEvent::PRE_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent')),
array(IndexResetEvent::POST_INDEX_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\IndexResetEvent'))
));
$this->aliasProcessor->expects($this->once())
->method('switchIndexAlias')
->with($indexConfig, $index, false);
$this->elasticaClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
array('index1/', 'DELETE'),
array('index1/', 'PUT', array(), array())
);
$this->resetter->resetIndex('index1');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testFailureWhenMissingIndexDoesntDispatch()
{
$this->configManager->expects($this->once())
->method('getIndexConfiguration')
->with('nonExistant')
->will($this->throwException(new \InvalidArgumentException));
$this->indexManager->expects($this->never())
->method('getIndex');
$this->resetter->resetIndex('nonExistant');
}
public function testResetType()
{
$typeConfig = new TypeConfig('type', array(), array());
$this->mockType('type', 'index', $typeConfig);
$this->dispatcherExpects(array(
array(TypeResetEvent::PRE_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent')),
array(TypeResetEvent::POST_TYPE_RESET, $this->isInstanceOf('FOS\\ElasticaBundle\\Event\\TypeResetEvent'))
));
$this->elasticaClient->expects($this->exactly(2))
->method('request')
->withConsecutive(
array('index/type/', 'DELETE'),
array('index/type/_mapping', 'PUT', array('type' => array()), array())
);
$this->resetter->resetIndexType('index', 'type');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testNonExistantResetType()
{
$this->configManager->expects($this->once())
->method('getTypeConfiguration')
->with('index', 'nonExistant')
->will($this->throwException(new \InvalidArgumentException));
$this->indexManager->expects($this->never())
->method('getIndex');
$this->resetter->resetIndexType('index', 'nonExistant');
}
public function testPostPopulateWithoutAlias()
{
$this->mockIndex('index', new IndexConfig('index', array(), array()));
$this->indexManager->expects($this->never())
->method('getIndex');
$this->aliasProcessor->expects($this->never())
->method('switchIndexAlias');
$this->resetter->postPopulate('index');
}
public function testPostPopulate()
{
$indexConfig = new IndexConfig('index', array(), array( 'useAlias' => true));
$index = $this->mockIndex('index', $indexConfig);
$this->aliasProcessor->expects($this->once())
->method('switchIndexAlias')
->with($indexConfig, $index);
$this->resetter->postPopulate('index');
}
private function dispatcherExpects(array $events)
{
$expectation = $this->dispatcher->expects($this->exactly(count($events)))
->method('dispatch');
call_user_func_array(array($expectation, 'withConsecutive'), $events);
}
private function mockIndex($indexName, IndexConfig $config, $mapping = array())
{
$this->configManager->expects($this->atLeast(1))
->method('getIndexConfiguration')
->with($indexName)
->will($this->returnValue($config));
$index = new Index($this->elasticaClient, $indexName);
$this->indexManager->expects($this->any())
->method('getIndex')
->with($indexName)
->willReturn($index);
$this->mappingBuilder->expects($this->any())
->method('buildIndexMapping')
->with($config)
->willReturn($mapping);
return $index;
}
private function mockType($typeName, $indexName, TypeConfig $config, $mapping = array())
{
$this->configManager->expects($this->atLeast(1))
->method('getTypeConfiguration')
->with($indexName, $typeName)
->will($this->returnValue($config));
$index = new Index($this->elasticaClient, $indexName);
$this->indexManager->expects($this->once())
->method('getIndex')
->with($indexName)
->willReturn($index);
$this->mappingBuilder->expects($this->once())
->method('buildTypeMapping')
->with($config)
->willReturn($mapping);
return $index;
}
protected function setUp()
{
$this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
->disableOriginalConstructor()
->getMock();
$this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
->disableOriginalConstructor()
->getMock();
$this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
$this->dispatcher = $this->getMockBuilder('Symfony\\Component\\EventDispatcher\\EventDispatcherInterface')
->getMock();
$this->elasticaClient = $this->getMockBuilder('Elastica\\Client')
->disableOriginalConstructor()
->getMock();
$this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
$this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
->disableOriginalConstructor()
->getMock();
$this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder')
->disableOriginalConstructor()
->getMock();
$this->resetter = new Resetter($this->configManager, $this->indexManager, $this->aliasProcessor, $this->mappingBuilder);
/*$this->indexConfigsByName = array(
'foo' => array(
'index' => $this->getMockElasticaIndex(),
'config' => array(
'properties' => array(
'a' => array(
'dynamic_templates' => array(),
'properties' => array(),
),
'b' => array('properties' => array()),
),
),
),
'bar' => array(
'index' => $this->getMockElasticaIndex(),
'config' => array(
'properties' => array(
'a' => array('properties' => array()),
'b' => array('properties' => array()),
),
),
),
'parent' => array(
'index' => $this->getMockElasticaIndex(),
'config' => array(
'properties' => array(
'a' => array(
'properties' => array(
'field_2' => array()
),
'_parent' => array(
'type' => 'b',
'property' => 'b',
'identifier' => 'id'
),
),
'b' => array('properties' => array()),
),
),
),
);*/
}
public function testResetAllIndexes()
{
$this->configManager->expects($this->once())
->method('getIndexNames')
->will($this->returnValue(array('index1')));
$this->configManager->expects($this->once())
->method('getIndexConfiguration')
->with('index1')
->will($this->returnValue(new IndexConfig('index1', array(), array())));
$this->indexManager->expects($this->once())
->method('getIndex')
->with('index1')
->will($this->returnValue());
/*$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('create')
->with($this->indexConfigsByName['foo']['config'], true);
$this->indexConfigsByName['bar']['index']->expects($this->once())
->method('create')
->with($this->indexConfigsByName['bar']['config'], true);
$resetter = new Resetter($this->indexConfigsByName);*/
$this->resetter->resetAllIndexes();
}
public function testResetIndex()
{
$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('create')
->with($this->indexConfigsByName['foo']['config'], true);
$this->indexConfigsByName['bar']['index']->expects($this->never())
->method('create');
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndex('foo');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testResetIndexShouldThrowExceptionForInvalidIndex()
{
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndex('baz');
}
public function testResetIndexType()
{
$type = $this->getMockElasticaType();
$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('getType')
->with('a')
->will($this->returnValue($type));
$type->expects($this->once())
->method('delete');
$mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
$mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
$type->expects($this->once())
->method('setMapping')
->with($mapping);
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('foo', 'a');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testResetIndexTypeShouldThrowExceptionForInvalidIndex()
{
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('baz', 'a');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testResetIndexTypeShouldThrowExceptionForInvalidType()
{
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('foo', 'c');
}
public function testResetIndexTypeIgnoreTypeMissingException()
{
$type = $this->getMockElasticaType();
$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('getType')
->with('a')
->will($this->returnValue($type));
$type->expects($this->once())
->method('delete')
->will($this->throwException(new ResponseException(
new Request(''),
new Response(array('error' => 'TypeMissingException[[de_20131022] type[bla] missing]', 'status' => 404)))
));
$mapping = Mapping::create($this->indexConfigsByName['foo']['config']['properties']['a']['properties']);
$mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['properties']['a']['dynamic_templates']);
$type->expects($this->once())
->method('setMapping')
->with($mapping);
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('foo', 'a');
}
public function testIndexMappingForParent()
{
$type = $this->getMockElasticaType();
$this->indexConfigsByName['parent']['index']->expects($this->once())
->method('getType')
->with('a')
->will($this->returnValue($type));
$type->expects($this->once())
->method('delete');
$mapping = Mapping::create($this->indexConfigsByName['parent']['config']['properties']['a']['properties']);
$mapping->setParam('_parent', array('type' => 'b'));
$type->expects($this->once())
->method('setMapping')
->with($mapping);
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('parent', 'a');
}
/**
* @return \Elastica\Index
*/
private function getMockElasticaIndex()
{
return $this->getMockBuilder('Elastica\Index')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \Elastica\Type
*/
private function getMockElasticaType()
{
return $this->getMockBuilder('Elastica\Type')
->disableOriginalConstructor()
->getMock();
$this->resetter = new Resetter(
$this->configManager,
$this->indexManager,
$this->aliasProcessor,
$this->mappingBuilder,
$this->dispatcher
);
}
}

View file

@ -203,7 +203,7 @@ class ObjectPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer()
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
$transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}

View file

@ -112,7 +112,7 @@ class ObjectSerializerPersisterTest extends \PHPUnit_Framework_TestCase
private function getTransformer()
{
$transformer = new ModelToElasticaIdentifierTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
$transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}

View file

@ -55,6 +55,7 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
{
return array(
array('nonexistentEntityMethod'),
array(array('@indexableService', 'internalMethod')),
array(array(new IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
@ -67,10 +68,13 @@ class IndexableTest extends \PHPUnit_Framework_TestCase
array('isIndexable', false),
array(array(new IndexableDecider(), 'isIndexable'), true),
array(array('@indexableService', 'isIndexable'), true),
array(array('@indexableService'), true),
array(function (Entity $entity) { return $entity->maybeIndex(); }, true),
array('entity.maybeIndex()', true),
array('!object.isIndexable() && entity.property == "abc"', true),
array('entity.property != "abc"', false),
array('["array", "values"]', true),
array('[]', false)
);
}
@ -111,4 +115,9 @@ class IndexableDecider
protected function internalMethod()
{
}
public function __invoke($object)
{
return true;
}
}

View file

@ -57,9 +57,10 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase
/**
* @param string $testQuery
* @param int $testLimit
* @param mixed $testLimit
* @param string $method
* @return \FOS\ElasticaBundle\Finder\TransformedFinder|\PHPUnit_Framework_MockObject_MockObject
*
* @return \FOS\ElasticaBundle\Finder\TransformedFinder
*/
private function getFinderMock($testQuery, $testLimit = null, $method = 'find')
{

View file

@ -37,7 +37,7 @@ class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCa
$this->collection = new ElasticaToModelTransformerCollection($this->transformers = array(
'type1' => $transformer1,
'type2' => $transformer2,
), array());
));
}
public function testGetObjectClass()

View file

@ -2,6 +2,7 @@
namespace FOS\ElasticaBundle\Tests\Transformer\ModelToElasticaAutoTransformer;
use FOS\ElasticaBundle\Event\TransformEvent;
use FOS\ElasticaBundle\Transformer\ModelToElasticaAutoTransformer;
use Symfony\Component\PropertyAccess\PropertyAccess;
@ -125,6 +126,35 @@ class POPO
class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
public function testTransformerDispatches()
{
$dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')
->getMock();
$dispatcher->expects($this->once())
->method('dispatch')
->with(
TransformEvent::POST_TRANSFORM,
$this->isInstanceOf('FOS\ElasticaBundle\Event\TransformEvent')
);
$transformer = $this->getTransformer($dispatcher);
$transformer->transform(new POPO(), array());
}
public function testPropertyPath()
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array('name' => array('property_path' => false)));
$this->assertInstanceOf('Elastica\Document', $document);
$this->assertFalse($document->has('name'));
$document = $transformer->transform(new POPO(), array('realName' => array('property_path' => 'name')));
$this->assertInstanceOf('Elastica\Document', $document);
$this->assertTrue($document->has('realName'));
$this->assertEquals('someName', $document->get('realName'));
}
public function testThatCanTransformObject()
{
$transformer = $this->getTransformer();
@ -241,11 +271,11 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
{
$transformer = $this->getTransformer();
$document = $transformer->transform(new POPO(), array(
'sub' => array(
'type' => 'nested',
'properties' => array('foo' => '~'),
),
));
'sub' => array(
'type' => 'nested',
'properties' => array('foo' => array()),
),
));
$data = $document->getData();
$this->assertTrue(array_key_exists('sub', $data));
@ -291,7 +321,7 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
'foo' => 'foo',
'bar' => 'foo',
'id' => 1,
), $data['obj']);
), $data['obj']);
}
public function testObjectsMappingOfAtLeastOneAutoMappedObjectAndAtLeastOneManuallyMappedObject()
@ -380,12 +410,14 @@ class ModelToElasticaAutoTransformerTest extends \PHPUnit_Framework_TestCase
}
/**
* @param null|\Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
*
* @return ModelToElasticaAutoTransformer
*/
private function getTransformer()
private function getTransformer($dispatcher = null)
{
$transformer = new ModelToElasticaAutoTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
$transformer = new ModelToElasticaAutoTransformer(array(), $dispatcher);
$transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}

View file

@ -51,7 +51,7 @@ class ModelToElasticaIdentifierTransformerTest extends \PHPUnit_Framework_TestCa
private function getTransformer()
{
$transformer = new ModelToElasticaIdentifierTransformer();
$transformer->setPropertyAccessor(PropertyAccess::getPropertyAccessor());
$transformer->setPropertyAccessor(PropertyAccess::createPropertyAccessor());
return $transformer;
}

View file

@ -0,0 +1,51 @@
<?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\Transformer;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
{
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Set the PropertyAccessor instance.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Returns a sorting closure to be used with usort() to put retrieved objects
* back in the order that they were returned by ElasticSearch.
*
* @param array $idPos
* @param string $identifierPath
* @return callable
*/
protected function getSortingClosure(array $idPos, $identifierPath)
{
$propertyAccessor = $this->propertyAccessor;
return function ($a, $b) use ($idPos, $identifierPath, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifierPath)] > $idPos[$propertyAccessor->getValue($b, $identifierPath)];
};
}
}

View file

@ -81,7 +81,7 @@ class ElasticaToModelTransformerCollection implements ElasticaToModelTransformer
$objects = $this->transform($elasticaObjects);
$result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) {
for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
}

View file

@ -3,10 +3,17 @@
namespace FOS\ElasticaBundle\Transformer;
/**
* Maps Elastica documents with model objects.
* Indicates that the model should have elastica highlights injected.
*/
interface HighlightableModelInterface
{
/**
* Returns a unique identifier for the model.
*
* @return mixed
*/
public function getId();
/**
* Set ElasticSearch highlight data.
*

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle\Transformer;
use FOS\ElasticaBundle\Event\TransformEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Elastica\Document;
@ -12,6 +14,11 @@ use Elastica\Document;
*/
class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterface
{
/**
* @var EventDispatcherInterface
*/
protected $dispatcher;
/**
* Optional parameters.
*
@ -31,11 +38,13 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
/**
* Instanciates a new Mapper.
*
* @param array $options
* @param array $options
* @param EventDispatcherInterface $dispatcher
*/
public function __construct(array $options = array())
public function __construct(array $options = array(), EventDispatcherInterface $dispatcher = null)
{
$this->options = array_merge($this->options, $options);
$this->dispatcher = $dispatcher;
}
/**
@ -66,16 +75,24 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
$property = (null !== $mapping['property']) ? $mapping['property'] : $mapping['type'];
$value = $this->propertyAccessor->getValue($object, $property);
$document->setParent($this->propertyAccessor->getValue($value, $mapping['identifier']));
continue;
}
$value = $this->propertyAccessor->getValue($object, $key);
$path = isset($mapping['property_path']) ?
$mapping['property_path'] :
$key;
if (false === $path) {
continue;
}
$value = $this->propertyAccessor->getValue($object, $path);
if (isset($mapping['type']) && in_array($mapping['type'], array('nested', 'object')) && isset($mapping['properties']) && !empty($mapping['properties'])) {
/* $value is a nested document or object. Transform $value into
* an array of documents, respective the mapped properties.
*/
$document->set($key, $this->transformNested($value, $mapping['properties']));
continue;
}
@ -86,12 +103,20 @@ class ModelToElasticaAutoTransformer implements ModelToElasticaTransformerInterf
} else {
$document->addFileContent($key, $value);
}
continue;
}
$document->set($key, $this->normalizeValue($value));
}
if ($this->dispatcher) {
$event = new TransformEvent($document, $fields, $object);
$this->dispatcher->dispatch(TransformEvent::POST_TRANSFORM, $event);
$document = $event->getDocument();
}
return $document;
}

View file

@ -21,32 +21,24 @@
"psr/log": "~1.0"
},
"require-dev":{
"doctrine/orm": "~2.2",
"doctrine/doctrine-bundle": "~1.2@beta",
"doctrine/orm": "~2.4",
"doctrine/doctrine-bundle": "~1.2",
"jms/serializer-bundle": "@stable",
"phpunit/phpunit": "~4.1",
"propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev",
"pagerfanta/pagerfanta": "~1.0",
"knplabs/knp-components": "~1.2",
"knplabs/knp-paginator-bundle": "~2.4",
"symfony/browser-kit" : "~2.3",
"symfony/expression-language" : "~2.4",
"symfony/twig-bundle": "~2.3"
},
"suggest": {
"doctrine/orm": "~2.2",
"doctrine/mongodb-odm": "1.0.*@dev",
"propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev",
"knplabs/knp-components": "~1.2",
"symfony/expression-language" : "~2.4"
},
"autoload": {
"psr-4": { "FOS\\ElasticaBundle\\": "" }
},
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev"
"dev-master": "3.2.x-dev"
}
}
}