Compare commits

...

353 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 b6c252aac3 Update the changelog for 3.0.9 2015-03-12 17:54:08 +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
Christophe Coevoet f72c51503a Fix the service definitions when the logger is not set in listener
The empty tag is parsed to an empty string, not to null. And this is not
a valid value for the service
2015-03-12 17:44:57 +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 2215d07ff8 Merge pull request #818 from merk/cs-fixes-30
php-cs-fixer for 3.0.x
2015-03-12 21:58:21 +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 e796d6179b Elastica, Doctrine\Common and Doctrine\ORM are required for tests 2015-03-12 21:21:18 +11:00
Tim Nagel dd388e4b25 CS fixes 2015-03-12 21:20:00 +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
Evgeniy Sokolov cf9f7c6be8 fix error for empty type configuration 2015-03-11 15:29:17 +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 55bfee22e8 Cache composer 2015-03-10 22:00:09 +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 2401b1083c Bump version 2015-01-31 18:33:07 +11:00
Tim Nagel 8e627ee011 Merge pull request #788 from FriendsOfSymfony/elastica-bump
Update Elastica dependency
2015-01-31 18:31:08 +11:00
Tim Nagel 6992beeb47 Update Elastica dependency 2015-01-31 18:24:47 +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 e5d9c3ddbb Merge branch 'pr/732' into 3.0.x 2015-01-25 19:41:57 +11:00
Tim Nagel 906e2e0749 Ability to set connectionStrategy for elastica clients 2015-01-25 19:38:01 +11:00
Tim Nagel 81f5f983c0 Fix line break 2015-01-25 19:06:41 +11:00
Tim Nagel 030b194c7b Fix tests 2015-01-22 11:49:28 +11:00
Tim Nagel 7f28be3c4e Merge branch '3.0.x' 2015-01-22 11:26:07 +11:00
Tim Nagel 79e263d7a7 Merge pull request #779 from merk/index-name-doc
Clarified what index_name does. Closes #731
2015-01-22 11:25:47 +11:00
Tim Nagel e772ca6450 Fix php 5.3 compatibility 2015-01-22 11:23:51 +11:00
Tim Nagel 55abe132c6 Update changelog 2015-01-22 09:36:33 +11:00
Tim Nagel aef5940578 Merge branch 'pr/760' into 3.0.x 2015-01-22 09:34:10 +11:00
Tim Nagel b9b0c1b961 Move TypeConfig creation to its own method 2015-01-22 09:33:46 +11:00
Michaël Perrin 64fa26e3d9 Fix PHP notice when using indexes without defined types 2015-01-22 09:24:59 +11:00
Tim Nagel c4a2858265 Merge branch 'pr/772' into 3.0.x 2015-01-22 09:23:24 +11:00
CedCannes 7471c13d75 Update manual-provider.md
Typo in class path
2015-01-22 09:22:34 +11:00
Tim Nagel c901d60552 Clarified what index_name does. Closes #731 2015-01-22 09:15:38 +11:00
Tim Nagel 401446e1c4 Add integration testing around _parent mapping 2015-01-22 09:06:21 +11:00
Christophe Coevoet 1cea135dc5 Merge branch '3.0.x' 2015-01-21 18:11:18 +01:00
Christophe Coevoet 32d190f554 Bump the changelog for 3.0.7 2015-01-21 18:10:33 +01:00
Christophe Coevoet 5060fa4d4a Move the test file to a better location
The test of the DI extension is not a functional test.
2015-01-21 17:51:56 +01:00
Christophe Coevoet b4c01f3641 Merge branch 'formapro-forks/fixing-bug-with-parent' into 3.0.x 2015-01-21 17:43:36 +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 2ce2d7e610 Add test for multi_field 2015-01-09 08:55:57 +11:00
Vladimir Kartaviy 905265ea0e "multi_field" type fields are not normalized
Fix for #764
2015-01-09 08:47:15 +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 9f5ce217dc Release 3.0.6 2015-01-04 22:08:20 +11:00
Tim Nagel 6ef6092f3f Update 3.0 changeling 2015-01-04 22:03:19 +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
Evan Owens 156884527c Update example
FOS\ElasticaBundle\Client has been deprecated.
2015-01-04 20:01:04 +11:00
DjangoFR 5eaff9e61b removed unused image (being base64 encoded - see #742) 2015-01-04 19:49:30 +11:00
Danijel Brkic 3975ed3d5b Built-in templates use a base64 encoded image for the toolbar 2015-01-04 19:49:25 +11:00
Tim Nagel d33e064801 Merge commit '1f7acc5' into 3.0.x 2014-12-24 09:10:41 +11:00
Tim Nagel 1f7acc563a Ignore strings starting with @ 2014-12-24 09:09:45 +11:00
Christophe Coevoet eaa32cbf22 Fix the BC layer for indexable callbacks
Using services was not based on a @ prefix in the string in the old API
but based on the existence of the class.
2014-12-23 15:21:25 +01: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
Christophe Coevoet 97848ca0d0 Removed the testing on Symfony dev-master
dev-master is Symfony 3.0, and we are not yet marking it as compatible
2014-12-17 14:23:00 +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
Nikolai Zujev c45dcd955d Clean filtered objects if the entire batch was filtered away, to prevent memory allocation issue. 2014-12-01 09:47:32 +11: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 67ae044309 #724 Fix debug_logging option on the provider 2014-10-08 08:59:45 +11: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
Michael Schramm b3f87e414f move classes to parametes 2014-09-21 21:08:41 +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 428a1014ca Move query logging into its own method 2014-09-21 20:12:30 +10:00
Tim Nagel 1d5fe44ca4 Fix CS from scrutiniser-ci 2014-09-21 20:12:30 +10:00
Tim Nagel cf586a4ef4 Merge branch '3.0.x' 2014-09-04 09:39:54 +10:00
Tim Nagel c4210a5c6d Fix previous merge 2014-09-04 09:37:27 +10:00
Tim Nagel 029ebb153a Merge remote-tracking branch 'upstream/pr/705' into 3.0.x
Conflicts:
	Index/AliasProcessor.php
2014-09-04 09:26:53 +10:00
Tim Nagel 736163551c Merge remote-tracking branch 'upstream/pr/704' into 3.0.x 2014-09-04 09:17:34 +10:00
Patrick McAndrew 76dcd2f62e fix warning if no aliases
PHP Warning:  array_keys() expects parameter 1 to be array, AliasProcessor.php on line 128
2014-08-29 15:52:46 +01:00
Patrick McAndrew 2958833012 Ability to delete an index if expecting an alias 2014-08-28 17:59:58 +01: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 a7a23b92cb Merge pull request #698 from merk/new-testing
Update testing
2014-08-21 23:15:38 +10:00
Tim Nagel 598a59927e Update travis testing 2014-08-21 22:54:12 +10:00
Patrick McAndrew 0425379420 add back fos_elastica.client tag that was removed in e78950ddb7 2014-08-21 22:18:53 +10:00
Tim Nagel 380727afbe Merge pull request #695 from notFloran/fix-search-annotation
Use the new Search annotation
2014-08-21 21:54:23 +10:00
Floran Brutel 69c2214bc5 Use the new Search annotation
Use "FOS\ElasticaBundle\Annotation\Search" instead of "FOS\ElasticaBundle\Configuration\Search" in the repository manager.
Update the cookbook
2014-08-18 13:30:45 +02:00
Tim Nagel af5fb7e97e Merge pull request #694 from notFloran/update-version-setup
Update version in setup.md
2014-08-17 21:13:11 +10:00
Floran Brutel 22a2a223cc Update version in setup.md
Use "~3.0" instead of "~3.0.2" to get version 3.0.3 and future minor
versions
2014-08-17 12:16:15 +02:00
Tim Nagel c08d86124a Merge branch '3.0.x' 2014-08-11 08:44:22 +10:00
Luis Cordova 33ee047f83 fix dependency on elastic extension
i had a weird error in which just installing the bundle with "friendsofsymfony/elastica-bundle":     "~3.0.2",
game me a version back even before it had a composer! 😊
i checked and found out composer gets really confused or glitchy with packages not following semver.
In any case 1.3.0 is out, and i remove the 4th digit which i think composer ignores totally or gets confused about.
2014-08-11 08:44:15 +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 420135dc85 Merge branch 'travis-fixing' into 3.0.x 2014-08-08 10:00:55 +10:00
Tim Nagel 20033709cf Fix empty mappings for old ES versions 2014-08-08 09:59:24 +10:00
Tim Nagel 229d4cb982 Merge branch '3.0.x' 2014-08-08 08:36:36 +10:00
Tim Nagel 84cf6c79c2 Merge remote-tracking branch 'upstream/pr/684' into 3.0.x 2014-08-08 08:33:59 +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
Josh Worden f5987a48b9 BC Break: Restored noDeepMerging to Configuration
When performNoDeepMerging is not used, Symfony environment-specific server configurations no longer work.
2014-08-07 16:33:14 -05:00
Tim Nagel dafe8abe0e Output ES version 2014-08-07 09:32:30 +10:00
Tim Nagel 0d22c20d37 Merge remote-tracking branch 'upstream/pr/682' into 3.0.x 2014-08-07 09:26:11 +10:00
Tim Nagel f9eb6577d1 Add tests to cover no mappings for serializer enabled type 2014-08-07 09:25:49 +10:00
Tim Nagel c44f676224 Test mappings key being null still causes appropriate configuration changes 2014-08-07 09:25:09 +10:00
Luis Cordova 9296534d30 Update setup.md 2014-08-06 17:25:05 -05:00
Tim Nagel f6e018f011 Merge remote-tracking branch 'upstream/pr/679' into 3.0.x 2014-08-04 08:53:14 +10:00
Lukasz Cybula 9a5b80e723 Ignore missing Doctrine results during hybridTransform() 2014-07-31 14:13:01 +02:00
Tim Nagel eaa6c2e085 Merge branch '3.0.x' 2014-07-31 15:55:37 +10:00
Tim Nagel 64c5c19831 Merge branch 'pr/675' into 3.0.x
Closes #675
2014-07-31 15:54:53 +10:00
Pablo 9befa90f41 Return repository in Manager Class MongoDB 2014-07-31 15:54:22 +10:00
esodin 659468ae3a Issue: Parent is missing in the fields list that causes RoutingMissingException on flushing - test 2014-07-24 17:36:45 +03:00
esodin 001b38cf59 Issue: Parent is missing in the fields list that causes RoutingMissingException on flushing - test 2014-07-24 17:07:22 +03:00
esodin 1ef55b1239 Issue: Parent is missing in the fields list that causes RoutingMissingException on flushing - test 2014-07-24 17:03:26 +03:00
Tim Nagel c748ec64e9 Merge pull request #672 from merk/gh663
Fix indexable callbacks being overwritten by another index
2014-07-23 23:10:24 +10:00
Tobias Nyholm 11ee25cfea Added PHP 5.6 2014-07-23 21:47:10 +10:00
Tim Nagel e5410a5b65 Fix indexable callbacks being overwritten by another index
closes #663
2014-07-23 21:38:46 +10:00
Tim Nagel 9fbc622929 Merge pull request #671 from merk/completion-type-fix
Fix completion type
2014-07-23 20:06:33 +10:00
Tim Nagel fad481d822 Fix completion type 2014-07-23 20:00:14 +10:00
esodin 714502fa1f Issue: Parent is missing in the fields list that causes RoutingMissingException on flushing 2014-07-17 16:01:10 +03:00
Tim Nagel 7cedc5ba5a Merge pull request #653 from notFloran/fix-doc
Fix "Faceted Searching" doc
2014-07-08 09:35:40 +10:00
Floran Brutel d797af5b80 Fix "Faceted Searching" doc 2014-07-07 18:35:23 +02:00
Tim Nagel cdaf7105e0 Bump dev version to 3.1 2014-07-05 15:14:29 +10:00
Tim Nagel d88d96bf55 Fix invalid service reference in production 2014-07-04 22:10:24 +10:00
Tim Nagel bf38e59e49 Documentation on aliased repopulation 2014-07-04 13:07:35 +10:00
Tim Nagel 90a554f627 Merge branch 'pr/622' 2014-07-04 12:49:13 +10:00
tamirvs d57d430ab3 Ignore iterator keys when converting to array 2014-07-04 12:48:27 +10:00
Tim Nagel be4af0d1af Merge pull request #644 from merk/elastica-connections
Rename servers to connections
2014-07-04 11:28:44 +10:00
Tim Nagel 425aa3d3e1 Merge pull request #645 from merk/stopwatch
Add stopwatch support for Elastica\Client
2014-07-04 11:25:37 +10:00
Tim Nagel 21e5d906a7 Simplify *One methods in the persister 2014-07-03 23:37:44 +10:00
Tim Nagel 815b836cdc Add stopwatch support for Elastica\Client 2014-07-03 23:20:18 +10:00
Tim Nagel 55dcf6859a Rename servers to connections 2014-07-03 21:58:54 +10:00
Tim Nagel c200e8fdfd Add tests for attachment type 2014-07-03 20:21:01 +10:00
Maksim Kotlyar 965b319d82 [transformers] tell container that first argument is collection. 2014-07-01 14:40:03 +03:00
Tim Nagel 7fcbb64a15 Fix edge case for dynamic templates 2014-07-01 18:02:30 +10:00
Tim Nagel 5d65676659 Add tests and normalisation to support old dynamic_templates format 2014-07-01 17:59:22 +10:00
Tobias Schultze c9a24436f3 dynamic templates are a list of hashes and have a mapping key (not properties) 2014-06-30 16:19:10 +02:00
Tim Nagel 9f85db9876 Merge pull request #639 from RonXS/master
[FEATURE] Use static instantiation with max depth check
2014-06-30 08:41:50 +10:00
Ron van der Molen 78db0b9b63 [FEATURE] Use static instantiation with max depth check 2014-06-29 22:14:32 +02:00
Tim Nagel ad37a28356 Fix populating command setting alias before population 2014-06-27 15:08:56 +10:00
Tim Nagel 474cbfa979 Added test coverage for ES store 2014-06-26 19:51:16 +10:00
Tim Nagel 77f2b99a3e Fix Indexable tests 2014-06-26 18:01:34 +10:00
Tim Nagel 5cc8c2978f Merge pull request #635 from merk/fix-array-callable
Fix array format for indexable_callback
2014-06-26 17:47:14 +10:00
Tim Nagel 96dc613c71 Fix array format for indexable_callback 2014-06-26 17:46:29 +10:00
Tim Nagel 4eacb5f4c8 Merge pull request #634 from merk/more-mapping-tests
Fix ClassCastException when no settings are present
2014-06-26 17:32:01 +10:00
Tim Nagel a879d3c1c9 Fix ClassCastException when no settings are present 2014-06-26 17:31:20 +10:00
Tim Nagel ffa73db1d2 Merge pull request #621 from gcds/patch-1
Added elapsed item to toolbar and menu
2014-06-25 13:27:53 +10:00
Tim Nagel 4d52c327aa Add back missing KnpPaginatorSubscriber service 2014-06-25 13:24:02 +10:00
Tim Nagel 524474fdc6 Merge branch 'propel-test' 2014-06-25 13:18:15 +10:00
Tim Nagel ae03b3f3cf Fix propel service definition 2014-06-25 13:18:02 +10:00
Tim Nagel f6264f4149 Fix AbstractProvider tests 2014-06-24 10:30:31 +10:00
Tim Nagel 2437a098ba Fix anonymous service error 2014-06-24 10:20:15 +10:00
Tim Nagel ae3605828e Fix AbstractProvider test 2014-06-24 10:19:26 +10:00
Tim Nagel 2a20bb623c Merge branch 'typed-config'
Conflicts:
	composer.json
2014-06-24 09:09:29 +10:00
Tim Nagel 9a62187329 Fix undefined index_name notice 2014-06-23 23:50:52 +10:00
Tim Nagel ca6991d494 Fix serializer 2014-06-23 23:05:57 +10:00
Tim Nagel 95e445bd0d Merge pull request #623 from merk/provider-fix
Fix provider bailing if the indexable service filters an entire batch of objects
2014-06-19 12:18:57 +10:00
Tim Nagel c97f0f1ddf Fix provider bailing if the indexable service filters an entire batch of objects 2014-06-19 11:14:13 +10:00
Tim Nagel 4e990e0cee Fixed mapping issues 2014-06-19 00:14:41 +10:00
Aurimas Niekis b0749afaf1 Added elapsed item to toolbar and menu
Kind similar to doctrine toolbar item
2014-06-18 16:12:16 +03:00
Tim Nagel 3ae382c933 Add tests to make sure KnpPaginator plays nice 2014-06-18 20:02:05 +10:00
Tim Nagel 949ea6963f Revert "Make the class of fos_elastica.paginator.subscriber service configurable"
This reverts commit fe19df365a.
2014-06-18 19:55:33 +10:00
Tim Nagel b155f304e4 Move IndexManager's resolution to tagged index services 2014-06-18 16:49:57 +10:00
Tim Nagel afbaf875b9 Cache creation of indexes and types in Elastica to avoid recreation 2014-06-18 16:48:00 +10:00
Tim Nagel 8905b4248c Rename Manager to ConfigManager 2014-06-18 16:47:01 +10:00
Tim Nagel 5cbb8ce1b6 Merge pull request #591 from OskarStark/patch-1
use $this->container instead of $container in usage.md
2014-06-18 09:55:16 +10:00
Tim Nagel b3c0d4fd44 Merge pull request #600 from gido/patch-1
Make the class of `fos_elastica.paginator.subscriber` service configurable
2014-06-18 09:54:13 +10:00
Tim Nagel ec5c05e8be dev 2014-06-17 23:22:58 +10:00
Tim Nagel e78950ddb7 Merge branch 'master' into typed-config
Conflicts:
	CHANGELOG-3.0.md
	Client.php
	DependencyInjection/Configuration.php
	DependencyInjection/FOSElasticaExtension.php
	DynamicIndex.php
	Resources/config/config.xml
	Resources/config/mongodb.xml
	Resources/config/orm.xml
	Tests/DependencyInjection/ConfigurationTest.php
	composer.json
2014-06-17 22:42:15 +10:00
Tim Nagel 5f335c37ab Merge pull request #613 from merk/composer-update
Update composer.json
2014-06-17 22:38:56 +10:00
Tim Nagel 1e07d3c7fe Update composer.json 2014-06-17 22:19:19 +10:00
Tim Nagel 089e7f0d2e Add unstable badge
[skip ci]
2014-06-17 12:12:54 +10:00
Tim Nagel aafb8c8e89 Fix indexable callbacks with alias based indexes 2014-06-17 12:03:38 +10:00
Tim Nagel e225d841ed Wrong service. Whoops. 2014-06-17 10:54:09 +10:00
Tim Nagel b49437529c Fix incorrect provider configuration 2014-06-17 10:41:11 +10:00
Tim Nagel 94568d9554 Merge pull request #608 from merk/populate-indexable
Indexable callback refactoring, implemented callback in Provider
2014-06-17 10:11:46 +10:00
Tim Nagel 2e23f4a1bf Added documentation changes for indexable_callback 2014-06-17 10:07:10 +10:00
Tim Nagel 02d864f7e2 Merge pull request #593 from merk/gh-592
Fix nested property configuration
2014-06-17 09:48:22 +10:00
Tim Nagel 12797c60fa Merge pull request #609 from PeerJ/FixLoggerWithNullTransport
make sure headers is set prior to accessing
2014-06-17 09:44:43 +10:00
Patrick McAndrew 934c6af8b8 make PSR2 compliant 2014-06-16 14:30:03 +01:00
Tim Nagel 0383811834 Fix test, add test for failing mappings 2014-06-16 23:28:53 +10:00
Tim Nagel ada3942576 Config Source providers 2014-06-16 23:23:49 +10:00
Tim Nagel 813a4a5d26 Fix previous commits 2014-06-16 22:43:16 +10:00
Tim Nagel 7682d5a80a Update composer.json 2014-06-16 22:33:31 +10:00
Tim Nagel 64be10447d Move Search annotation 2014-06-16 22:33:04 +10:00
Patrick McAndrew 629ca0df2e make sure headers is set prior to accessing 2014-06-16 12:59:09 +01:00
Tim Nagel 391e18dcbf Update changelog 2014-06-16 16:20:17 +10:00
Tim Nagel e54cc3c243 Implement callback checking in the provider 2014-06-16 16:17:04 +10:00
Tim Nagel 66d2410999 Move Indexable callback calculations to a new service 2014-06-16 15:57:42 +10:00
Tim Nagel f5932a8e47 Merge remote-tracking branch 'origin/refactor' into typed-config
Conflicts:
	Resetter.php
2014-06-14 11:11:31 +10:00
Gilles Doge fe19df365a Make the class of fos_elastica.paginator.subscriber service configurable 2014-06-10 18:20:02 +02:00
Tim Nagel 14083496d7 Merge pull request #598 from merk/mapping-tests
Adds initial functional tests
2014-06-08 22:45:40 +10:00
Tim Nagel 5009673b6a Functional Tests v1 2014-06-08 22:35:38 +10:00
Tim Nagel 833feee207 Merge branch 'transport-option' of https://github.com/milan/FOSElasticaBundle into transport-option
Conflicts:
	DependencyInjection/Configuration.php
2014-06-08 18:00:17 +10:00
Tim Nagel f6b9e57a9c Merge pull request #595 from PeerJ/FixResetterException
fix method call
2014-06-08 17:56:15 +10:00
Patrick McAndrew 366fb39606 fix method call 2014-06-04 17:26:25 +01:00
Tim Nagel 8540f13bbf Fix nested property configuration
Fixes #592
2014-06-02 21:35:51 +10:00
Tim Nagel fa65784b47 Merge pull request #583 from merk/rename-mappings
Rename mappings to properties maintaining BC
2014-06-02 00:41:54 +10:00
Tim Nagel 1dc6856ef9 Configuration rework 2014-06-02 00:40:03 +10:00
Oskar Stark 6a822504bc use $this->container instead of $container 2014-06-01 15:57:29 +02:00
Tim Nagel c52c32fb56 Merge pull request #590 from Tornaldo/patch-1
Update usage.md
2014-06-01 16:38:37 +10:00
Tornaldo 2e0aa064a2 Update usage.md 2014-06-01 01:27:20 +02:00
Tim Nagel 12b724dd20 Merge pull request #585 from leberknecht/patch-2
fixing missing flush event handler
2014-05-26 10:17:11 +10:00
Delf Tonder 8060d3dcd7 fixing missing flush event handler
In [commit](843c76b6ca (diff-850942b3ba24ab03a40aaa81b6152852)) the configuration-definition for the flush listener was accidentally removed. 
As the flush listener is no longer set to be enabled in the extensions getDoctrineEvents method, the flush listener is not set. 
This results in a situation were we are only able to have the modified objects on the list for index-update, but never actually sending the update to the ES host.
2014-05-25 18:51:14 +02:00
Tim Nagel dad15d0b38 Deprecate top level classes 2014-05-25 20:51:46 +10:00
Tim Nagel 53180e2810 Bring tidy in line with property renaming 2014-05-25 20:14:51 +10:00
Tim Nagel 8e88505a3f Merge branch 'rename-mappings' into configuration-tidy 2014-05-25 20:13:21 +10:00
Tim Nagel a79fa0242e Simplified Configuration.php 2014-05-25 20:08:01 +10:00
Tim Nagel c38dc107e7 Rename mappings to properties maintaining BC
Fixes #407
2014-05-25 17:56:57 +10:00
Tim Nagel be89ccf825 Merge pull request #582 from tobiassjosten/urllessconfig
Don't default url
2014-05-25 14:56:42 +10:00
Tobias Sjösten f97e66712a Don't default url 2014-05-25 00:31:40 +01:00
Tim Nagel 5a84d55129 Merge pull request #580 from leberknecht/patch-1
update setup.md - immediate is an listener option
2014-05-24 23:10:44 +10:00
Delf Tonder f9745c8d21 update setup.md - immediate is an listener option
fixed yml config example (having immediate as an persistence option will result in parsing error)
2014-05-24 12:49:12 +02:00
Tim Nagel a9ea78443f Support Elastica proxy option 2014-05-24 00:17:59 +10:00
Tim Nagel 458b53240b Merge branch 'configuration-fixes' 2014-05-23 23:21:29 +10:00
Tim Nagel e77aa0c180 Test on php 5.5 2014-05-23 23:20:52 +10:00
Tim Nagel f20392d78b Fix test failures for DoctrineProvider 2014-05-23 23:19:55 +10:00
Tim Nagel f8a445b46c Fix disabling of logger in DoctrineProvider 2014-05-23 23:11:45 +10:00
Tim Nagel d532e6b1e3 Merge pull request #579 from merk/client-overwrite
Surpress server errors cookbook update
2014-05-23 23:01:16 +10:00
Tim Nagel 3addfffc91 Added logging of server errors to example 2014-05-23 23:00:34 +10:00
Tim Nagel 18143449cc Merge branch 'patch-2' of https://github.com/stloyd/FOSElasticaBundle into client-overwrite 2014-05-23 22:52:58 +10:00
Tim Nagel b09c7fb50e Merge branch 'master' of https://github.com/edast/FOSElasticaBundle 2014-05-23 22:51:11 +10:00
Tim Nagel 352e3b68ac Add configuration tests 2014-05-23 22:49:03 +10:00
Tim Nagel 41c4d77b20 Move serializer node to its own method, add serializer to type_prototype 2014-05-23 22:49:03 +10:00
Tim Nagel 843c76b6ca Move persistence node to its own method 2014-05-23 22:49:02 +10:00
Tim Nagel 6d2b7a8367 Combine client normalisation into a single method 2014-05-23 22:48:19 +10:00
Tim Nagel 57fbc70015 Merge pull request #522 from merk/doctrine-provider-speedup
Doctrine provider speedup
2014-05-23 22:46:36 +10:00
Tim Nagel 2029aba76a Ability for FOSElasticaBundle to disable persistence backend logging for population
Update documentation and changelog
2014-05-23 22:46:14 +10:00
Tim Nagel 6253d3f8df Merge pull request #553 from benniekrijger/issue-552-geoshape-mapping
Added GeoShape mapping options
2014-05-23 22:03:58 +10:00
Darius Staisiunas 28d0ee925d added support for geohash 2014-05-23 12:55:33 +03:00
Milan Magudia 2c208a4f10 Allow other transport options to be used i.e. Http, Https, Guzzle etc... 2014-05-22 16:18:08 +01:00
Tim Nagel 70ad5c9b37 Merge pull request #576 from milan/#534-cluster-logger-default
Fix for Issue #543 Client has a dependency on a non-existent service "%kernel.debug%"
2014-05-21 20:58:04 +10:00
Milan Magudia e1bbb87cfe Fix for Issue #543 Client has a dependency on a non-existent service "%kernel.debug%" 2014-05-21 10:24:44 +01:00
Tim Nagel 6748c9c623 Merge pull request #571 from tPl0ch/feature-flush-event
Make it possible to disable flush event through configuration
2014-05-20 08:50:50 +10:00
Tim Nagel 72e7b77dae Merge pull request #562 from evillemez/config
stop config from adding empty arrays into type mappings
2014-05-20 08:49:26 +10:00
Tim Nagel 1c5339ac40 Merge pull request #560 from cassianotartari/master
Update Configuration.php
2014-05-20 08:48:51 +10:00
Thomas Ploch e5754ef5fc Make it possible to disable flush event through configuration 2014-05-13 13:13:06 +02:00
Tim Nagel 6bbe61f319 Merge pull request #567 from caponica/ToModelTransformer_andWhere
Changed QueryBuilder method from ->where() to ->andWhere() ...
2014-05-08 09:43:16 +10:00
caponica b0841c18ec Changed QueryBuilder method from ->where() to ->andWhere() so it works with customised QueryBuilders which have an existing where clause (instead of over-writing any existing DQL 'where' part) 2014-05-08 00:26:30 +01:00
Tim Nagel 62f6cf0f8a Merge pull request #565 from FriendsOfSymfony/elasticsearch-1.1
Elasticsearch 1.*
2014-05-06 16:39:45 +10:00
Lea Haensenberger 5da8ee1a16 still supporting 0.9 versions of elastica 2014-05-06 08:18:37 +02:00
Lea Haensenberger b1d64e358d Also cleanup fields in properties of objects 2014-05-05 13:39:36 +02:00
Lea Haensenberger eaa9f83997 remove empty fields arrays from mapping, this is not ignored anymore by elasticsearch 1.* 2014-05-05 13:39:36 +02:00
Lea Haensenberger ae02364e7c use elastica library 1.1 and higher 2014-05-05 13:39:36 +02:00
Tim Nagel ca50617776 Merge pull request #563 from PeerJ/UpdateElasticaDependency
Update Elastica Dependency
2014-05-03 11:28:52 +10:00
Patrick McAndrew d9f3fa1a59 commit 1dcaadbe6f
Persister/ObjectPersister.php
line 112: $this->type->updateDocuments($documents);
introduces a dependency on Elastica 0.9.10.0
2014-05-02 15:07:15 +01:00
Evan Villemez c93bbb9081 stop config from adding empty arrays into type mappings 2014-05-01 11:05:16 -04:00
Cassiano 39f1033a34 Update Configuration.php
Adding the option to set index_analyzer and search_analyzer to _all field.
2014-04-24 09:39:22 -03:00
Tim Nagel 90abc44968 Merge pull request #556 from ahmedmhmd/master
Revert declaring PaginateElasticaQuerySubscriber as a parameter, so not to break Knp compiler pass
2014-04-20 16:01:38 +10:00
Ahmed Mohamed c9fd1cc5d9 Revert declaring PaginateElasticaQuerySubscriber as a paremeter, so not to break Knp compiler pass 2014-04-20 06:11:20 +02:00
ben 2bd6aba7ef Added GeoShape mapping options 2014-04-18 13:57:08 +02:00
Joseph Bielawski f15ca02859 Fix documentation for client overwriting. 2014-03-31 11:59:37 +02:00
155 changed files with 6817 additions and 2502 deletions

5
.scrutinizer.yml Normal file
View file

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

View file

@ -1,9 +1,36 @@
language: php
cache:
directories:
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
matrix:
include:
- php: 5.5
env: SYMFONY_VERSION='2.3.*'
- php: 5.5
env: SYMFONY_VERSION='2.5.*'
before_script:
- echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- /usr/share/elasticsearch/bin/elasticsearch -v
- sudo /usr/share/elasticsearch/bin/plugin -install elasticsearch/elasticsearch-mapper-attachments/2.0.0
- sudo service elasticsearch restart
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'
- sh -c 'if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony=$SYMFONY_VERSION; fi;'
- composer install --dev --prefer-source
script:
- vendor/bin/phpunit --coverage-clover=coverage.clover
services:
- elasticsearch
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover

16
Annotation/Search.php Normal file
View file

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

View file

@ -12,13 +12,61 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.0...v3.0.1
To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x`
* 3.0.0-ALPHA3 (xxxx-xx-xx)
* 3.0.9 (2015-03-12)
* Fix a bug in the BC layer of the type configuration for empty configs
* Fix the service definition for the Doctrine listener when the logger is not enabled
* 3.0.8 (2014-01-31)
* Fixed handling of empty indexes #760
* Added support for `connectionStrategy` Elastica configuration #732
* Allow Elastica 1.4
* 3.0.7 (2015-01-21)
* Fixed the indexing of parent/child relations, broken since 3.0 #774
* Fixed multi_field properties not being normalised #769
* 3.0.6 (2015-01-04)
* Removed unused public image asset for the web development toolbar #742
* Fixed is_indexable_callback BC code to support array notation #761
* Fixed debug_logger for type providers #724
* Clean the OM if we filter away the entire batch #737
* 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called
`indexable_callback` which is run when both populating and listening for object
changes.
* AbstractProvider constructor change: Second argument is now an `IndexableInterface`
instance.
* Annotation @Search moved to FOS\ElasticaBundle\Annotation\Search with FOS\ElasticaBundle\Configuration\Search deprecated
* Deprecated FOS\ElasticaBundle\Client in favour of FOS\ElasticaBundle\Elastica\Client
* Deprecated FOS\ElasticaBundle\DynamicIndex in favour of FOS\ElasticaBundle\Elastica\Index
* Deprecated FOS\ElasticaBundle\IndexManager in favour of FOS\ElasticaBundle\Index\IndexManager
* Deprecated FOS\ElasticaBundle\Resetter in favour of FOS\ElasticaBundle\Index\Resetter
* 3.0.0-ALPHA5 (2014-05-23)
* Doctrine Provider speed up by disabling persistence logging while populating documents
* 3.0.0-ALPHA4 (2014-04-10)
* Indexes are now capable of logging errors with Elastica
* Fixed deferred indexing of deleted documents
* Resetting an index will now create it even if it doesn't exist
* Bulk upserting of documents is now supported when populating
* 3.0.0-ALPHA3 (2014-04-01)
* a9c4c93: Logger is now only enabled in debug mode by default
* #463: allowing hot swappable reindexing
* #415: BC BREAK: document indexing occurs in postFlush rather than the pre* events previously.
* 7d13823: Dropped (broken) support for Symfony <2.3
* #496: Added support for HTTP headers
* #528: FOSElasticaBundle will disable Doctrine logging when populating for a large increase in speed
* 3.0.0-ALPHA2 (2014-03-17)

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,43 +2,11 @@
namespace FOS\ElasticaBundle;
use Elastica\Client as ElasticaClient;
use Elastica\Request;
use FOS\ElasticaBundle\Logger\ElasticaLogger;
use FOS\ElasticaBundle\Elastica\Client as BaseClient;
/**
* @author Gordon Franke <info@nevalon.de>
* @deprecated Use \FOS\ElasticaBundle\Elastica\LoggingClient
*/
class Client extends ElasticaClient
class Client extends BaseClient
{
/**
* {@inheritdoc}
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
$start = microtime(true);
$response = parent::request($path, $method, $data, $query);
if (null !== $this->_logger and $this->_logger instanceof ElasticaLogger) {
$time = microtime(true) - $start;
$connection = $this->getLastRequest()->getConnection();
$connection_array = array(
'host' => $connection->getHost(),
'port' => $connection->getPort(),
'transport' => $connection->getTransport(),
'headers' => $connection->getConfig('headers'),
);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
}
return $response;
}
public function getIndex($name)
{
return new DynamicIndex($this, $name);
}
}

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
* 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);
$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

@ -10,7 +10,7 @@ use FOS\ElasticaBundle\IndexManager;
use FOS\ElasticaBundle\Resetter;
/**
* Reset search indexes
* Reset search indexes.
*/
class ResetCommand extends ContainerAwareCommand
{
@ -33,6 +33,7 @@ class ResetCommand extends ContainerAwareCommand
->setName('fos:elastica:reset')
->addOption('index', null, InputOption::VALUE_OPTIONAL, 'The index to reset')
->addOption('type', null, InputOption::VALUE_OPTIONAL, 'The type to reset')
->addOption('force', null, InputOption::VALUE_NONE, 'Force index deletion if same name as alias')
->setDescription('Reset search indexes')
;
}
@ -51,8 +52,9 @@ class ResetCommand extends ContainerAwareCommand
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$index = $input->getOption('index');
$type = $input->getOption('type');
$index = $input->getOption('index');
$type = $input->getOption('type');
$force = (bool) $input->getOption('force');
if (null === $index && null !== $type) {
throw new \InvalidArgumentException('Cannot specify type option without an index.');
@ -69,7 +71,7 @@ class ResetCommand extends ContainerAwareCommand
foreach ($indexes as $index) {
$output->writeln(sprintf('<info>Resetting</info> <comment>%s</comment>', $index));
$this->resetter->resetIndex($index);
$this->resetter->resetIndex($index, false, $force);
}
}
}

View file

@ -11,7 +11,7 @@ use Elastica\Query;
use Elastica\Result;
/**
* Searches a type
* Searches a type.
*/
class SearchCommand extends ContainerAwareCommand
{

View file

@ -0,0 +1,64 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration;
/**
* Central manager for index and type configuration.
*/
class ConfigManager implements ManagerInterface
{
/**
* @var IndexConfig[]
*/
private $indexes = array();
/**
* @param Source\SourceInterface[] $sources
*/
public function __construct(array $sources)
{
foreach ($sources as $source) {
$this->indexes = array_merge($source->getConfiguration(), $this->indexes);
}
}
public function getIndexConfiguration($indexName)
{
if (!$this->hasIndexConfiguration($indexName)) {
throw new \InvalidArgumentException(sprintf('Index with name "%s" is not configured.', $indexName));
}
return $this->indexes[$indexName];
}
public function getIndexNames()
{
return array_keys($this->indexes);
}
public function getTypeConfiguration($indexName, $typeName)
{
$index = $this->getIndexConfiguration($indexName);
$type = $index->getType($typeName);
if (!$type) {
throw new \InvalidArgumentException(sprintf('Type with name "%s" on index "%s" is not configured', $typeName, $indexName));
}
return $type;
}
public function hasIndexConfiguration($indexName)
{
return isset($this->indexes[$indexName]);
}
}

View file

@ -0,0 +1,124 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration;
class IndexConfig
{
/**
* The name of the index for ElasticSearch.
*
* @var string
*/
private $elasticSearchName;
/**
* The internal name of the index. May not be the same as the name used in ElasticSearch,
* especially if aliases are enabled.
*
* @var string
*/
private $name;
/**
* An array of settings sent to ElasticSearch when creating the index.
*
* @var array
*/
private $settings;
/**
* All types that belong to this index.
*
* @var TypeConfig[]
*/
private $types;
/**
* Indicates if the index should use an alias, allowing an index repopulation to occur
* without overwriting the current index.
*
* @var bool
*/
private $useAlias = false;
/**
* Constructor expects an array as generated by the Container Configuration builder.
*
* @param string $name
* @param TypeConfig[] $types
* @param array $config
*/
public function __construct($name, array $types, array $config)
{
$this->elasticSearchName = isset($config['elasticSearchName']) ? $config['elasticSearchName'] : $name;
$this->name = $name;
$this->settings = isset($config['settings']) ? $config['settings'] : array();
$this->types = $types;
$this->useAlias = isset($config['useAlias']) ? $config['useAlias'] : false;
}
/**
* @return string
*/
public function getElasticSearchName()
{
return $this->elasticSearchName;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return array
*/
public function getSettings()
{
return $this->settings;
}
/**
* @param string $typeName
*
* @return TypeConfig
*
* @throws \InvalidArgumentException
*/
public function getType($typeName)
{
if (!array_key_exists($typeName, $this->types)) {
throw new \InvalidArgumentException(sprintf('Type "%s" does not exist on index "%s"', $typeName, $this->name));
}
return $this->types[$typeName];
}
/**
* @return \FOS\ElasticaBundle\Configuration\TypeConfig[]
*/
public function getTypes()
{
return $this->types;
}
/**
* @return boolean
*/
public function isUseAlias()
{
return $this->useAlias;
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration;
/**
* Central manager for index and type configuration.
*/
interface ManagerInterface
{
/**
* Returns configuration for an index.
*
* @param $index
*
* @return IndexConfig
*/
public function getIndexConfiguration($index);
/**
* Returns an array of known index names.
*
* @return array
*/
public function getIndexNames();
/**
* Returns a type configuration.
*
* @param string $index
* @param string $type
*
* @return TypeConfig
*/
public function getTypeConfiguration($index, $type);
}

View file

@ -1,16 +1,26 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration;
use FOS\ElasticaBundle\Annotation\Search as BaseSearch;
/**
* Annotation class for setting search repository.
*
* @author Richard Miller <info@limethinking.co.uk>
* @Annotation
*
* @deprecated Use FOS\ElasticaBundle\Annotation\Search instead
* @Target("CLASS")
*/
class Search
class Search extends BaseSearch
{
/** @var string */
public $repositoryClass;
}

View file

@ -0,0 +1,80 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration\Source;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;
/**
* Returns index and type configuration from the container.
*/
class ContainerSource implements SourceInterface
{
/**
* The internal container representation of information.
*
* @var array
*/
private $configArray;
public function __construct(array $configArray)
{
$this->configArray = $configArray;
}
/**
* Should return all configuration available from the data source.
*
* @return IndexConfig[]
*/
public function getConfiguration()
{
$indexes = array();
foreach ($this->configArray as $config) {
$types = $this->getTypes($config);
$index = new IndexConfig($config['name'], $types, array(
'elasticSearchName' => $config['elasticsearch_name'],
'settings' => $config['settings'],
'useAlias' => $config['use_alias'],
));
$indexes[$config['name']] = $index;
}
return $indexes;
}
/**
* Builds TypeConfig objects for each type.
*
* @param array $config
*
* @return array
*/
protected function getTypes($config)
{
$types = array();
if (isset($config['types'])) {
foreach ($config['types'] as $typeConfig) {
$types[$typeConfig['name']] = new TypeConfig(
$typeConfig['name'],
$typeConfig['mapping'],
$typeConfig['config']
);
// TODO: handle prototypes..
}
}
return $types;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration\Source;
/**
* Represents a source of index and type information (ie, the Container configuration or
* annotations).
*/
interface SourceInterface
{
/**
* Should return all configuration available from the data source.
*
* @return \FOS\ElasticaBundle\Configuration\IndexConfig[]
*/
public function getConfiguration();
}

View file

@ -0,0 +1,113 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Configuration;
class TypeConfig
{
/**
* @var array
*/
private $config;
/**
* @var array
*/
private $mapping;
/**
* @var string
*/
private $name;
public function __construct($name, array $mapping, array $config = array())
{
$this->config = $config;
$this->mapping = $mapping;
$this->name = $name;
}
/**
* @return bool|null
*/
public function getDateDetection()
{
return $this->getConfig('date_detection');
}
/**
* @return array
*/
public function getDynamicDateFormats()
{
return $this->getConfig('dynamic_date_formats');
}
/**
* @return string|null
*/
public function getIndexAnalyzer()
{
return $this->getConfig('index_analyzer');
}
/**
* @return array
*/
public function getMapping()
{
return $this->mapping;
}
/**
* @return string|null
*/
public function getModel()
{
return isset($this->config['persistence']['model']) ?
$this->config['persistence']['model'] :
null;
}
/**
* @return bool|null
*/
public function getNumericDetection()
{
return $this->getConfig('numeric_detection');
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string|null
*/
public function getSearchAnalyzer()
{
return $this->getConfig('search_analyzer');
}
/**
* @param string $key
*/
private function getConfig($key)
{
return isset($this->config[$key]) ?
$this->config[$key] :
null;
}
}

View file

@ -0,0 +1,36 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class ConfigSourcePass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('fos_elastica.config_manager')) {
return;
}
$sources = array();
foreach (array_keys($container->findTaggedServiceIds('fos_elastica.config_source')) as $id) {
$sources[] = new Reference($id);
}
$container->getDefinition('fos_elastica.config_manager')->replaceArgument(0, $sources);
}
}

View file

@ -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\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class IndexPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('fos_elastica.index_manager')) {
return;
}
$indexes = array();
foreach ($container->findTaggedServiceIds('fos_elastica.index') as $id => $tags) {
foreach ($tags as $tag) {
$indexes[$tag['name']] = new Reference($id);
}
}
$container->getDefinition('fos_elastica.index_manager')->replaceArgument(0, $indexes);
}
}

View file

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

View file

@ -31,7 +31,7 @@ class TransformerPass implements CompilerPassInterface
throw new InvalidArgumentException('The Transformer must have both a type and an index defined.');
}
$transformers[$tag['index']][$tag['type']]= new Reference($id);
$transformers[$tag['index']][$tag['type']] = new Reference($id);
}
}

View file

@ -15,17 +15,22 @@ class Configuration implements ConfigurationInterface
*/
private $supportedDrivers = array('orm', 'mongodb', 'propel');
private $configArray = array();
/**
* If the kernel is running in debug mode.
*
* @var bool
*/
private $debug;
public function __construct($configArray)
public function __construct($debug)
{
$this->configArray = $configArray;
$this->debug = $debug;
}
/**
* Generates the configuration tree.
*
* @return \Symfony\Component\Config\Definition\NodeInterface
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
@ -58,17 +63,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Generates the configuration tree.
*
* @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface
*/
public function getConfigTree()
{
return $this->getConfigTreeBuilder()->buildTree();
}
/**
* Adds the configuration for the "clients" key
* Adds the configuration for the "clients" key.
*/
private function addClientsSection(ArrayNodeDefinition $rootNode)
{
@ -79,49 +74,52 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('id')
->prototype('array')
->performNoDeepMerging()
// BC - Renaming 'servers' node to 'connections'
->beforeNormalization()
->ifTrue(function($v) { return isset($v['host']) && isset($v['port']); })
->then(function($v) {
return array(
'servers' => array(
array(
'host' => $v['host'],
'port' => $v['port'],
'logger' => isset($v['logger']) ? $v['logger'] : null,
'headers' => isset($v['headers']) ? $v['headers'] : null,
)
)
);
})
->ifTrue(function ($v) { return isset($v['servers']); })
->then(function ($v) {
$v['connections'] = $v['servers'];
unset($v['servers']);
return $v;
})
->end()
// Elastica names its properties with camel case, support both
->beforeNormalization()
->ifTrue(function($v) { return isset($v['url']); })
->then(function($v) {
return array(
'servers' => array(
array(
'url' => $v['url'],
'logger' => isset($v['logger']) ? $v['logger'] : null
)
)
);
})
->ifTrue(function ($v) { return isset($v['connection_strategy']); })
->then(function ($v) {
$v['connectionStrategy'] = $v['connection_strategy'];
unset($v['connection_strategy']);
return $v;
})
->end()
// If there is no connections array key defined, assume a single connection.
->beforeNormalization()
->ifTrue(function ($v) { return is_array($v) && !array_key_exists('connections', $v); })
->then(function ($v) {
return array(
'connections' => array($v),
);
})
->end()
->children()
->arrayNode('servers')
->arrayNode('connections')
->requiresAtLeastOneElement()
->prototype('array')
->fixXmlConfig('header')
->children()
->scalarNode('url')
->validate()
->ifTrue(function($url) { return substr($url, -1) !== '/'; })
->then(function($url) { return $url.'/'; })
->ifTrue(function ($url) { return $url && substr($url, -1) !== '/'; })
->then(function ($url) { return $url.'/'; })
->end()
->end()
->scalarNode('host')->end()
->scalarNode('port')->end()
->scalarNode('proxy')->end()
->scalarNode('logger')
->defaultValue('%kernel.debug%')
->defaultValue($this->debug ? 'fos_elastica.logger' : false)
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
->end()
@ -129,12 +127,14 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->scalarNode('transport')->end()
->scalarNode('timeout')->end()
->end()
->end()
->end()
->scalarNode('timeout')->end()
->scalarNode('headers')->end()
->scalarNode('connectionStrategy')->defaultValue('Simple')->end()
->end()
->end()
->end()
@ -143,7 +143,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Adds the configuration for the "indexes" key
* Adds the configuration for the "indexes" key.
*/
private function addIndexesSection(ArrayNodeDefinition $rootNode)
{
@ -167,61 +167,8 @@ class Configuration implements ConfigurationInterface
->children()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->scalarNode('persist')->defaultValue('postFlush')->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->append($this->getPersistenceNode())
->append($this->getSerializerNode())
->end()
->end()
->variableNode('settings')->defaultValue(array())->end()
@ -245,84 +192,75 @@ class Configuration implements ConfigurationInterface
->useAttributeAsKey('name')
->prototype('array')
->treatNullLike(array())
->beforeNormalization()
->ifNull()
->thenEmptyArray()
->end()
// BC - Renaming 'mappings' node to 'properties'
->beforeNormalization()
->ifTrue(function ($v) { return array_key_exists('mappings', $v); })
->then(function ($v) {
$v['properties'] = $v['mappings'];
unset($v['mappings']);
return $v;
})
->end()
// BC - Support the old is_indexable_callback property
->beforeNormalization()
->ifTrue(function ($v) {
return isset($v['persistence']) &&
isset($v['persistence']['listener']) &&
isset($v['persistence']['listener']['is_indexable_callback']);
})
->then(function ($v) {
$callback = $v['persistence']['listener']['is_indexable_callback'];
if (is_array($callback)) {
list($class) = $callback + array(null);
if ($class[0] !== '@' && is_string($class) && !class_exists($class)) {
$callback[0] = '@'.$class;
}
}
$v['indexable_callback'] = $callback;
unset($v['persistence']['listener']['is_indexable_callback']);
return $v;
})
->end()
// Support multiple dynamic_template formats to match the old bundle style
// and the way ElasticSearch expects them
->beforeNormalization()
->ifTrue(function ($v) { return isset($v['dynamic_templates']); })
->then(function ($v) {
$dt = array();
foreach ($v['dynamic_templates'] as $key => $type) {
if (is_int($key)) {
$dt[] = $type;
} else {
$dt[][$key] = $type;
}
}
$v['dynamic_templates'] = $dt;
return $v;
})
->end()
->children()
->arrayNode('serializer')
->addDefaultsIfNotSet()
->children()
->arrayNode('groups')
->treatNullLike(array())
->prototype('scalar')->end()
->end()
->scalarNode('version')->end()
->end()
->end()
->booleanNode('date_detection')->end()
->arrayNode('dynamic_date_formats')->prototype('scalar')->end()->end()
->scalarNode('index_analyzer')->end()
->booleanNode('numeric_detection')->end()
->scalarNode('search_analyzer')->end()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('model')->end()
->scalarNode('repository')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->booleanNode('immediate')->defaultFalse()->end()
->scalarNode('logger')
->defaultFalse()
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end()
->end()
->variableNode('indexable_callback')->end()
->append($this->getPersistenceNode())
->append($this->getSerializerNode())
->end()
->append($this->getIdNode())
->append($this->getMappingsNode())
->append($this->getPropertiesNode())
->append($this->getDynamicTemplateNode())
->append($this->getSourceNode())
->append($this->getBoostNode())
@ -338,27 +276,17 @@ class Configuration implements ConfigurationInterface
}
/**
* Returns the array node used for "mappings".
* Returns the array node used for "properties".
*/
protected function getMappingsNode()
protected function getPropertiesNode()
{
$builder = new TreeBuilder();
$node = $builder->root('mappings');
$node = $builder->root('properties');
$nestings = $this->getNestings();
$childrenNode = $node
$node
->useAttributeAsKey('name')
->prototype('array')
->validate()
->ifTrue(function($v) { return isset($v['fields']) && empty($v['fields']); })
->then(function($v) { unset($v['fields']); return $v; })
->end()
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
$this->addFieldConfig($childrenNode, $nestings);
->prototype('variable')
->treatNullLike(array());
return $node;
}
@ -372,209 +300,26 @@ class Configuration implements ConfigurationInterface
$node = $builder->root('dynamic_templates');
$node
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('match')->end()
->scalarNode('unmatch')->end()
->scalarNode('match_mapping_type')->end()
->scalarNode('path_match')->end()
->scalarNode('path_unmatch')->end()
->scalarNode('match_pattern')->end()
->append($this->getDynamicTemplateMapping())
->end()
->end()
;
return $node;
}
/**
* @return the array node used for mapping in dynamic templates
*/
protected function getDynamicTemplateMapping()
{
$builder = new TreeBuilder();
$node = $builder->root('mapping');
$nestings = $this->getNestingsForDynamicTemplates();
$this->addFieldConfig($node->children(), $nestings);
return $node;
}
/**
* @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the field config to
* @param array $nestings the nested mappings for the current field level
*/
protected function addFieldConfig($node, $nestings)
{
$node
->scalarNode('type')->defaultValue('string')->end()
->scalarNode('boost')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->scalarNode('analyzer')->end()
->scalarNode('term_vector')->end()
->scalarNode('null_value')->end()
->booleanNode('include_in_all')->defaultValue(true)->end()
->booleanNode('enabled')->defaultValue(true)->end()
->scalarNode('lat_lon')->end()
->scalarNode('index_name')->end()
->booleanNode('omit_norms')->end()
->scalarNode('index_options')->end()
->scalarNode('ignore_above')->end()
->scalarNode('position_offset_gap')->end()
->arrayNode('_parent')
->treatNullLike(array())
->children()
->scalarNode('type')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->end()
->end()
->scalarNode('format')->end()
->scalarNode('similarity')->end();
;
if (isset($nestings['fields'])) {
$this->addNestedFieldConfig($node, $nestings, 'fields');
}
if (isset($nestings['properties'])) {
$node
->booleanNode('include_in_parent')->end()
->booleanNode('include_in_root')->end()
;
$this->addNestedFieldConfig($node, $nestings, 'properties');
}
}
/**
* @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the nested config to
* @param array $nestings The nestings for the current field level
* @param string $property the name of the nested property ('fields' or 'properties')
*/
protected function addNestedFieldConfig($node, $nestings, $property)
{
$childrenNode = $node
->arrayNode($property)
->useAttributeAsKey('name')
->prototype('array')
->validate()
->ifTrue(function($v) { return isset($v['fields']) && empty($v['fields']); })
->then(function($v) { unset($v['fields']); return $v; })
->end()
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
$this->addFieldConfig($childrenNode, $nestings[$property]);
$childrenNode
->children()
->scalarNode('match')->end()
->scalarNode('unmatch')->end()
->scalarNode('match_mapping_type')->end()
->scalarNode('path_match')->end()
->scalarNode('path_unmatch')->end()
->scalarNode('match_pattern')->end()
->arrayNode('mapping')
->prototype('variable')
->treatNullLike(array())
->end()
->end()
->end()
->end()
->end()
;
}
/**
* @return array The unique nested mappings for all types
*/
protected function getNestings()
{
if (!isset($this->configArray[0]['indexes'])) {
return array();
}
$nestings = array();
foreach ($this->configArray[0]['indexes'] as $index) {
if (empty($index['types'])) {
continue;
}
foreach ($index['types'] as $type) {
if (empty($type['mappings'])) {
continue;
}
$nestings = array_merge_recursive($nestings, $this->getNestingsForType($type['mappings'], $nestings));
}
}
return $nestings;
}
/**
* @return array The unique nested mappings for all dynamic templates
*/
protected function getNestingsForDynamicTemplates()
{
if (!isset($this->configArray[0]['indexes'])) {
return array();
}
$nestings = array();
foreach ($this->configArray[0]['indexes'] as $index) {
if (empty($index['types'])) {
continue;
}
foreach ($index['types'] as $type) {
if (empty($type['dynamic_templates'])) {
continue;
}
foreach ($type['dynamic_templates'] as $definition) {
$field = $definition['mapping'];
if (isset($field['fields'])) {
$this->addPropertyNesting($field, $nestings, 'fields');
} else if (isset($field['properties'])) {
$this->addPropertyNesting($field, $nestings, 'properties');
}
}
}
}
return $nestings;
}
/**
* @param array $mappings The mappings for the current type
* @return array The nested mappings defined for this type
*/
protected function getNestingsForType(array $mappings = null)
{
if ($mappings === null) {
return array();
}
$nestings = array();
foreach ($mappings as $field) {
if (isset($field['fields'])) {
$this->addPropertyNesting($field, $nestings, 'fields');
} else if (isset($field['properties'])) {
$this->addPropertyNesting($field, $nestings, 'properties');
}
}
return $nestings;
}
/**
* @param array $field The field mapping definition
* @param array $nestings The nestings array
* @param string $property The nested property name ('fields' or 'properties')
*/
protected function addPropertyNesting($field, &$nestings, $property)
{
if (!isset($nestings[$property])) {
$nestings[$property] = array();
}
$nestings[$property] = array_merge_recursive($nestings[$property], $this->getNestingsForType($field[$property]));
return $node;
}
/**
@ -614,7 +359,7 @@ class Configuration implements ConfigurationInterface
->end()
->scalarNode('compress')->end()
->scalarNode('compress_threshold')->end()
->scalarNode('enabled')->end()
->scalarNode('enabled')->defaultTrue()->end()
->end()
;
@ -677,7 +422,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Returns the array node used for "_all"
* Returns the array node used for "_all".
*/
protected function getAllNode()
{
@ -687,6 +432,8 @@ class Configuration implements ConfigurationInterface
$node
->children()
->scalarNode('enabled')->defaultValue(true)->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->end()
;
@ -694,7 +441,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Returns the array node used for "_timestamp"
* Returns the array node used for "_timestamp".
*/
protected function getTimestampNode()
{
@ -715,7 +462,7 @@ class Configuration implements ConfigurationInterface
}
/**
* Returns the array node used for "_ttl"
* Returns the array node used for "_ttl".
*/
protected function getTtlNode()
{
@ -733,4 +480,102 @@ class Configuration implements ConfigurationInterface
return $node;
}
/**
* @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition
*/
protected function getPersistenceNode()
{
$builder = new TreeBuilder();
$node = $builder->root('persistence');
$node
->validate()
->ifTrue(function ($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function ($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
->scalarNode('driver')
->validate()
->ifNotInArray($this->supportedDrivers)
->thenInvalid('The driver %s is not supported. Please choose one of '.json_encode($this->supportedDrivers))
->end()
->end()
->scalarNode('model')->end()
->scalarNode('repository')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->arrayNode('provider')
->children()
->scalarNode('batch_size')->defaultValue(100)->end()
->scalarNode('clear_object_manager')->defaultTrue()->end()
->scalarNode('debug_logging')
->defaultValue($this->debug)
->treatNullLike(true)
->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('listener')
->children()
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->scalarNode('flush')->defaultTrue()->end()
->booleanNode('immediate')->defaultFalse()->end()
->scalarNode('logger')
->defaultFalse()
->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger')
->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('finder')
->children()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('elastica_to_model_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('hydrate')->defaultTrue()->end()
->scalarNode('ignore_missing')->defaultFalse()->end()
->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end()
->scalarNode('service')->end()
->end()
->end()
->arrayNode('model_to_elastica_transformer')
->addDefaultsIfNotSet()
->children()
->scalarNode('service')->end()
->end()
->end()
->end();
return $node;
}
/**
* @return ArrayNodeDefinition|\Symfony\Component\Config\Definition\Builder\NodeDefinition
*/
protected function getSerializerNode()
{
$builder = new TreeBuilder();
$node = $builder->root('serializer');
$node
->addDefaultsIfNotSet()
->children()
->arrayNode('groups')
->treatNullLike(array())
->prototype('scalar')->end()
->end()
->scalarNode('version')->end()
->end();
return $node;
}
}

View file

@ -4,9 +4,7 @@ namespace FOS\ElasticaBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
@ -14,15 +12,32 @@ use InvalidArgumentException;
class FOSElasticaExtension extends Extension
{
protected $indexConfigs = array();
protected $typeFields = array();
protected $loadedDrivers = array();
protected $serializerConfig = array();
/**
* Definition of elastica clients as configured by this extension.
*
* @var array
*/
private $clients = array();
/**
* An array of indexes as configured by the extension.
*
* @var array
*/
private $indexConfigs = array();
/**
* If we've encountered a type mapped to a specific persistence driver, it will be loaded
* here.
*
* @var array
*/
private $loadedDrivers = array();
public function load(array $configs, ContainerBuilder $container)
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
@ -31,7 +46,9 @@ class FOSElasticaExtension extends Extension
return;
}
$loader->load('config.xml');
foreach (array('config', 'index', 'persister', 'provider', 'source', 'transformer') as $basename) {
$loader->load(sprintf('%s.xml', $basename));
}
if (empty($config['default_client'])) {
$keys = array_keys($config['clients']);
@ -43,41 +60,53 @@ class FOSElasticaExtension extends Extension
$config['default_index'] = reset($keys);
}
$clientIdsByName = $this->loadClients($config['clients'], $container);
$this->serializerConfig = isset($config['serializer']) ? $config['serializer'] : null;
$indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']);
$indexRefsByName = array_map(function($id) {
return new Reference($id);
}, $indexIdsByName);
if (isset($config['serializer'])) {
$loader->load('serializer.xml');
$this->loadIndexManager($indexRefsByName, $container);
$this->loadResetter($this->indexConfigs, $container);
$this->loadSerializer($config['serializer'], $container);
}
$this->loadClients($config['clients'], $container);
$container->setAlias('fos_elastica.client', sprintf('fos_elastica.client.%s', $config['default_client']));
$this->loadIndexes($config['indexes'], $container);
$container->setAlias('fos_elastica.index', sprintf('fos_elastica.index.%s', $config['default_index']));
$container->getDefinition('fos_elastica.config_source.container')->replaceArgument(0, $this->indexConfigs);
$this->loadIndexManager($container);
$this->createDefaultManagerAlias($config['default_manager'], $container);
}
/**
* @param array $config
* @param ContainerBuilder $container
*
* @return Configuration
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($config);
return new Configuration($container->getParameter('kernel.debug'));
}
/**
* Loads the configured clients.
*
* @param array $clients An array of clients configurations
* @param array $clients An array of clients configurations
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @return array
*/
protected function loadClients(array $clients, ContainerBuilder $container)
private function loadClients(array $clients, ContainerBuilder $container)
{
$clientIds = array();
foreach ($clients as $name => $clientConfig) {
$clientId = sprintf('fos_elastica.client.%s', $name);
$clientDef = new Definition('%fos_elastica.client.class%', array($clientConfig));
$logger = $clientConfig['servers'][0]['logger'];
$clientDef = new DefinitionDecorator('fos_elastica.client_prototype');
$clientDef->replaceArgument(0, $clientConfig);
$logger = $clientConfig['connections'][0]['logger'];
if (false !== $logger) {
$clientDef->addMethodCall('setLogger', array(new Reference($logger)));
}
@ -85,78 +114,75 @@ class FOSElasticaExtension extends Extension
$container->setDefinition($clientId, $clientDef);
$clientIds[$name] = $clientId;
$this->clients[$name] = array(
'id' => $clientId,
'reference' => new Reference($clientId),
);
}
return $clientIds;
}
/**
* Loads the configured indexes.
*
* @param array $indexes An array of indexes configurations
* @param array $indexes An array of indexes configurations
* @param ContainerBuilder $container A ContainerBuilder instance
* @param array $clientIdsByName
* @param $defaultClientName
* @param $serializerConfig
*
* @throws \InvalidArgumentException
*
* @return array
*/
protected function loadIndexes(array $indexes, ContainerBuilder $container, array $clientIdsByName, $defaultClientName)
private function loadIndexes(array $indexes, ContainerBuilder $container)
{
$indexIds = array();
foreach ($indexes as $name => $index) {
if (isset($index['client'])) {
$clientName = $index['client'];
if (!isset($clientIdsByName[$clientName])) {
throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName));
}
} else {
$clientName = $defaultClientName;
}
$indexableCallbacks = array();
$clientId = $clientIdsByName[$clientName];
foreach ($indexes as $name => $index) {
$indexId = sprintf('fos_elastica.index.%s', $name);
$indexName = isset($index['index_name']) ? $index['index_name'] : $name;
$indexDefArgs = array($indexName);
$indexDef = new Definition('%fos_elastica.index.class%', $indexDefArgs);
$indexDef->setFactoryService($clientId);
$indexDef->setFactoryMethod('getIndex');
$container->setDefinition($indexId, $indexDef);
$typePrototypeConfig = isset($index['type_prototype']) ? $index['type_prototype'] : array();
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array(
'index' => new Reference($indexId),
'name_or_alias' => $indexName,
'config' => array(
'mappings' => array()
)
);
if ($index['finder']) {
$this->loadIndexFinder($container, $name, $indexId);
}
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
if ($index['use_alias']) {
$this->indexConfigs[$name]['use_alias'] = true;
$indexDef = new DefinitionDecorator('fos_elastica.index_prototype');
$indexDef->replaceArgument(0, $indexName);
$indexDef->addTag('fos_elastica.index', array(
'name' => $name,
));
if (isset($index['client'])) {
$client = $this->getClient($index['client']);
$indexDef->setFactoryService($client);
}
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig);
$container->setDefinition($indexId, $indexDef);
$reference = new Reference($indexId);
$this->indexConfigs[$name] = array(
'elasticsearch_name' => $indexName,
'reference' => $reference,
'name' => $name,
'settings' => $index['settings'],
'type_prototype' => isset($index['type_prototype']) ? $index['type_prototype'] : array(),
'use_alias' => $index['use_alias'],
);
if ($index['finder']) {
$this->loadIndexFinder($container, $name, $reference);
}
$this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name], $indexableCallbacks);
}
return $indexIds;
$indexable = $container->getDefinition('fos_elastica.indexable');
$indexable->replaceArgument(0, $indexableCallbacks);
}
/**
* Loads the configured index finders.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param string $name The index name
* @param string $indexId The index service identifier
* @param string $name The index name
* @param Reference $index Reference to the related index
*
* @return string
*/
protected function loadIndexFinder(ContainerBuilder $container, $name, $indexId)
private function loadIndexFinder(ContainerBuilder $container, $name, Reference $index)
{
/* Note: transformer services may conflict with "collection.index", if
* an index and type names were "collection" and an index, respectively.
@ -167,166 +193,141 @@ class FOSElasticaExtension extends Extension
$finderId = sprintf('fos_elastica.finder.%s', $name);
$finderDef = new DefinitionDecorator('fos_elastica.finder');
$finderDef->replaceArgument(0, new Reference($indexId));
$finderDef->replaceArgument(0, $index);
$finderDef->replaceArgument(1, new Reference($transformerId));
$container->setDefinition($finderId, $finderDef);
return $finderId;
}
/**
* Loads the configured types.
*
* @param array $types An array of types configurations
* @param ContainerBuilder $container A ContainerBuilder instance
* @param $indexName
* @param $indexId
* @param array $typePrototypeConfig
* @param $serializerConfig
* @param array $types
* @param ContainerBuilder $container
* @param array $indexConfig
* @param array $indexableCallbacks
*/
protected function loadTypes(array $types, ContainerBuilder $container, $indexName, $indexId, array $typePrototypeConfig)
private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig, array &$indexableCallbacks)
{
foreach ($types as $name => $type) {
$type = self::deepArrayUnion($typePrototypeConfig, $type);
$typeId = sprintf('%s.%s', $indexId, $name);
$typeDefArgs = array($name);
$typeDef = new Definition('%fos_elastica.type.class%', $typeDefArgs);
$typeDef->setFactoryService($indexId);
$typeDef->setFactoryMethod('getType');
if ($this->serializerConfig) {
$callbackDef = new Definition($this->serializerConfig['callback_class']);
$callbackId = sprintf('%s.%s.serializer.callback', $indexId, $name);
$indexName = $indexConfig['name'];
$typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize')));
$callbackDef->addMethodCall('setSerializer', array(new Reference($this->serializerConfig['serializer'])));
if (isset($type['serializer']['groups'])) {
$callbackDef->addMethodCall('setGroups', array($type['serializer']['groups']));
}
if (isset($type['serializer']['version'])) {
$callbackDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$callbackClassImplementedInterfaces = class_implements($this->serializerConfig['callback_class']); // PHP < 5.4 friendly
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$callbackDef->addMethodCall('setContainer', array(new Reference('service_container')));
}
$container->setDefinition($callbackId, $callbackDef);
$typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize')));
}
$typeId = sprintf('%s.%s', $indexConfig['reference'], $name);
$typeDef = new DefinitionDecorator('fos_elastica.type_prototype');
$typeDef->replaceArgument(0, $name);
$typeDef->setFactoryService($indexConfig['reference']);
$container->setDefinition($typeId, $typeDef);
$this->indexConfigs[$indexName]['config']['mappings'][$name] = array(
"_source" => array("enabled" => true), // Add a default setting for empty mapping settings
$typeConfig = array(
'name' => $name,
'mapping' => array(), // An array containing anything that gets sent directly to ElasticSearch
'config' => array(),
);
if (isset($type['_id'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_id'] = $type['_id'];
}
if (isset($type['_source'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_source'] = $type['_source'];
}
if (isset($type['_boost'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_boost'] = $type['_boost'];
}
if (isset($type['_routing'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_routing'] = $type['_routing'];
}
if (isset($type['mappings']) && !empty($type['mappings'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['properties'] = $type['mappings'];
$typeName = sprintf('%s/%s', $indexName, $name);
$this->typeFields[$typeName] = $type['mappings'];
}
if (isset($type['_parent'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_parent'] = array('type' => $type['_parent']['type']);
$typeName = sprintf('%s/%s', $indexName, $name);
$this->typeFields[$typeName]['_parent'] = $type['_parent'];
}
if (isset($type['persistence'])) {
$this->loadTypePersistenceIntegration($type['persistence'], $container, $typeDef, $indexName, $name);
}
if (isset($type['index_analyzer'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['index_analyzer'] = $type['index_analyzer'];
}
if (isset($type['search_analyzer'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['search_analyzer'] = $type['search_analyzer'];
}
if (isset($type['index'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['index'] = $type['index'];
}
if (isset($type['_all'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_all'] = $type['_all'];
}
if (isset($type['_timestamp'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_timestamp'] = $type['_timestamp'];
}
if (isset($type['_ttl'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['_ttl'] = $type['_ttl'];
}
if (!empty($type['dynamic_templates'])) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'] = array();
foreach ($type['dynamic_templates'] as $templateName => $templateData) {
$this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'][] = array($templateName => $templateData);
foreach (array(
'dynamic_templates',
'properties',
'_all',
'_boost',
'_id',
'_parent',
'_routing',
'_source',
'_timestamp',
'_ttl',
) as $field) {
if (isset($type[$field])) {
$typeConfig['mapping'][$field] = $type[$field];
}
}
}
}
/**
* Merges two arrays without reindexing numeric keys.
*
* @param array $array1 An array to merge
* @param array $array2 An array to merge
*
* @return array The merged array
*/
static protected function deepArrayUnion($array1, $array2)
{
foreach ($array2 as $key => $value) {
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
$array1[$key] = self::deepArrayUnion($array1[$key], $value);
} else {
$array1[$key] = $value;
foreach (array(
'persistence',
'serializer',
'index_analyzer',
'search_analyzer',
'date_detection',
'dynamic_date_formats',
'numeric_detection',
) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] :
null;
}
$this->indexConfigs[$indexName]['types'][$name] = $typeConfig;
if (isset($type['persistence'])) {
$this->loadTypePersistenceIntegration($type['persistence'], $container, new Reference($typeId), $indexName, $name);
$typeConfig['persistence'] = $type['persistence'];
}
if (isset($type['indexable_callback'])) {
$indexableCallbacks[sprintf('%s/%s', $indexName, $name)] = $type['indexable_callback'];
}
if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$typeSerializerId = sprintf('%s.serializer.callback', $typeId);
$typeSerializerDef = new DefinitionDecorator('fos_elastica.serializer_callback_prototype');
if (isset($type['serializer']['groups'])) {
$typeSerializerDef->addMethodCall('setGroups', array($type['serializer']['groups']));
}
if (isset($type['serializer']['version'])) {
$typeSerializerDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$typeDef->addMethodCall('setSerializer', array(array(new Reference($typeSerializerId), 'serialize')));
$container->setDefinition($typeSerializerId, $typeSerializerDef);
}
}
return $array1;
}
/**
* Loads the optional provider and finder for a type
* Loads the optional provider and finder for a type.
*
* @param array $typeConfig
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param \Symfony\Component\DependencyInjection\Definition $typeDef
* @param $indexName
* @param $typeName
* @param array $typeConfig
* @param ContainerBuilder $container
* @param Reference $typeRef
* @param string $indexName
* @param string $typeName
*/
protected function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Definition $typeDef, $indexName, $typeName)
private function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Reference $typeRef, $indexName, $typeName)
{
$this->loadDriver($container, $typeConfig['driver']);
$elasticaToModelTransformerId = $this->loadElasticaToModelTransformer($typeConfig, $container, $indexName, $typeName);
$modelToElasticaTransformerId = $this->loadModelToElasticaTransformer($typeConfig, $container, $indexName, $typeName);
$objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId);
$objectPersisterId = $this->loadObjectPersister($typeConfig, $typeRef, $container, $indexName, $typeName, $modelToElasticaTransformerId);
if (isset($typeConfig['provider'])) {
$this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName);
$this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $indexName, $typeName);
}
if (isset($typeConfig['finder'])) {
$this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName);
$this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeRef, $indexName, $typeName);
}
if (isset($typeConfig['listener'])) {
$this->loadTypeListener($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName);
$this->loadTypeListener($typeConfig, $container, $objectPersisterId, $indexName, $typeName);
}
}
protected function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
/**
* Creates and loads an ElasticaToModelTransformer.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
*
* @return string
*/
private function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
{
if (isset($typeConfig['elastica_to_model_transformer']['service'])) {
return $typeConfig['elastica_to_model_transformer']['service'];
}
/* Note: transformer services may conflict with "prototype.driver", if
* the index and type names were "prototype" and a driver, respectively.
*/
@ -339,55 +340,78 @@ class FOSElasticaExtension extends Extension
$argPos = ('propel' === $typeConfig['driver']) ? 0 : 1;
$serviceDef->replaceArgument($argPos, $typeConfig['model']);
$serviceDef->replaceArgument($argPos + 1, array(
'hydrate' => $typeConfig['elastica_to_model_transformer']['hydrate'],
'identifier' => $typeConfig['identifier'],
'ignore_missing' => $typeConfig['elastica_to_model_transformer']['ignore_missing'],
'query_builder_method' => $typeConfig['elastica_to_model_transformer']['query_builder_method']
));
$serviceDef->replaceArgument($argPos + 1, array_merge($typeConfig['elastica_to_model_transformer'], array(
'identifier' => $typeConfig['identifier'],
)));
$container->setDefinition($serviceId, $serviceDef);
return $serviceId;
}
protected function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
/**
* Creates and loads a ModelToElasticaTransformer for an index/type.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
*
* @return string
*/
private function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
{
if (isset($typeConfig['model_to_elastica_transformer']['service'])) {
return $typeConfig['model_to_elastica_transformer']['service'];
}
if ($this->serializerConfig) {
$abstractId = sprintf('fos_elastica.model_to_elastica_identifier_transformer');
} else {
$abstractId = sprintf('fos_elastica.model_to_elastica_transformer');
}
$abstractId = $container->hasDefinition('fos_elastica.serializer_callback_prototype') ?
'fos_elastica.model_to_elastica_identifier_transformer' :
'fos_elastica.model_to_elastica_transformer';
$serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId);
$serviceDef->replaceArgument(0, array(
'identifier' => $typeConfig['identifier']
'identifier' => $typeConfig['identifier'],
));
$container->setDefinition($serviceId, $serviceDef);
return $serviceId;
}
protected function loadObjectPersister(array $typeConfig, Definition $typeDef, ContainerBuilder $container, $indexName, $typeName, $transformerId)
/**
* Creates and loads an object persister for a type.
*
* @param array $typeConfig
* @param Reference $typeRef
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
* @param string $transformerId
*
* @return string
*/
private function loadObjectPersister(array $typeConfig, Reference $typeRef, ContainerBuilder $container, $indexName, $typeName, $transformerId)
{
$arguments = array(
$typeDef,
$typeRef,
new Reference($transformerId),
$typeConfig['model'],
);
if ($this->serializerConfig) {
if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$abstractId = 'fos_elastica.object_serializer_persister';
$callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['index'], $typeName);
$callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['reference'], $typeName);
$arguments[] = array(new Reference($callbackId), 'serialize');
} else {
$abstractId = 'fos_elastica.object_persister';
$arguments[] = $this->typeFields[sprintf('%s/%s', $indexName, $typeName)];
$mapping = $this->indexConfigs[$indexName]['types'][$typeName]['mapping'];
$argument = $mapping['properties'];
if (isset($mapping['_parent'])) {
$argument['_parent'] = $mapping['_parent'];
}
$arguments[] = $argument;
}
$serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId);
foreach ($arguments as $i => $argument) {
@ -399,31 +423,58 @@ class FOSElasticaExtension extends Extension
return $serviceId;
}
protected function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName)
/**
* Loads a provider for a type.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $objectPersisterId
* @param string $indexName
* @param string $typeName
*
* @return string
*/
private function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
{
if (isset($typeConfig['provider']['service'])) {
return $typeConfig['provider']['service'];
}
/* Note: provider services may conflict with "prototype.driver", if the
* index and type names were "prototype" and a driver, respectively.
*/
$providerId = sprintf('fos_elastica.provider.%s.%s', $indexName, $typeName);
$providerDef = new DefinitionDecorator('fos_elastica.provider.prototype.' . $typeConfig['driver']);
$providerDef = new DefinitionDecorator('fos_elastica.provider.prototype.'.$typeConfig['driver']);
$providerDef->addTag('fos_elastica.provider', array('index' => $indexName, 'type' => $typeName));
$providerDef->replaceArgument(0, new Reference($objectPersisterId));
$providerDef->replaceArgument(1, $typeConfig['model']);
$providerDef->replaceArgument(2, $typeConfig['model']);
// Propel provider can simply ignore Doctrine-specific options
$providerDef->replaceArgument(2, array_diff_key($typeConfig['provider'], array('service' => 1)));
$providerDef->replaceArgument(3, array_merge(array_diff_key($typeConfig['provider'], array('service' => 1)), array(
'indexName' => $indexName,
'typeName' => $typeName,
)));
$container->setDefinition($providerId, $providerDef);
return $providerId;
}
protected function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName)
/**
* Loads doctrine listeners to handle indexing of new or updated objects.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $objectPersisterId
* @param string $indexName
* @param string $typeName
*
* @return string
*/
private function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
{
if (isset($typeConfig['listener']['service'])) {
return $typeConfig['listener']['service'];
}
/* Note: listener services may conflict with "prototype.driver", if the
* index and type names were "prototype" and a driver, respectively.
*/
@ -431,42 +482,42 @@ class FOSElasticaExtension extends Extension
$listenerId = sprintf('fos_elastica.listener.%s.%s', $indexName, $typeName);
$listenerDef = new DefinitionDecorator($abstractListenerId);
$listenerDef->replaceArgument(0, new Reference($objectPersisterId));
$listenerDef->replaceArgument(1, $typeConfig['model']);
$listenerDef->replaceArgument(2, $this->getDoctrineEvents($typeConfig));
$listenerDef->replaceArgument(3, $typeConfig['identifier']);
if ($typeConfig['listener']['logger']) {
$listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
}
$listenerDef->replaceArgument(2, array(
'identifier' => $typeConfig['identifier'],
'indexName' => $indexName,
'typeName' => $typeName,
));
$listenerDef->replaceArgument(3, $typeConfig['listener']['logger'] ?
new Reference($typeConfig['listener']['logger']) :
null
);
$tagName = null;
switch ($typeConfig['driver']) {
case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
case 'orm':
$tagName = 'doctrine.event_listener';
break;
case 'mongodb':
$tagName = 'doctrine_mongodb.odm.event_listener';
break;
}
if (isset($typeConfig['listener']['is_indexable_callback'])) {
$callback = $typeConfig['listener']['is_indexable_callback'];
if (is_array($callback)) {
list($class) = $callback + array(null);
if (is_string($class) && !class_exists($class)) {
$callback[0] = new Reference($class);
}
if (null !== $tagName) {
foreach ($this->getDoctrineEvents($typeConfig) as $event) {
$listenerDef->addTag($tagName, array('event' => $event));
}
$listenerDef->addMethodCall('setIsIndexableCallback', array($callback));
}
$container->setDefinition($listenerId, $listenerDef);
return $listenerId;
}
/**
* Map Elastica to Doctrine events for the current driver
* Map Elastica to Doctrine events for the current driver.
*/
private function getDoctrineEvents(array $typeConfig)
{
// Flush always calls depending on actions scheduled in lifecycle listeners
$typeConfig['listener']['flush'] = true;
switch ($typeConfig['driver']) {
case 'orm':
$eventsClass = '\Doctrine\ORM\Events';
@ -476,7 +527,6 @@ class FOSElasticaExtension extends Extension
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
break;
}
$events = array();
@ -484,7 +534,7 @@ class FOSElasticaExtension extends Extension
'insert' => array(constant($eventsClass.'::postPersist')),
'update' => array(constant($eventsClass.'::postUpdate')),
'delete' => array(constant($eventsClass.'::preRemove')),
'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush'))
'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush')),
);
foreach ($eventMapping as $event => $doctrineEvents) {
@ -496,14 +546,26 @@ class FOSElasticaExtension extends Extension
return $events;
}
protected function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, $typeDef, $indexName, $typeName)
/**
* Loads a Type specific Finder.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $elasticaToModelId
* @param Reference $typeRef
* @param string $indexName
* @param string $typeName
*
* @return string
*/
private function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, Reference $typeRef, $indexName, $typeName)
{
if (isset($typeConfig['finder']['service'])) {
$finderId = $typeConfig['finder']['service'];
} else {
$finderId = sprintf('fos_elastica.finder.%s.%s', $indexName, $typeName);
$finderDef = new DefinitionDecorator('fos_elastica.finder');
$finderDef->replaceArgument(0, $typeDef);
$finderDef->replaceArgument(0, $typeRef);
$finderDef->replaceArgument(1, new Reference($elasticaToModelId));
$container->setDefinition($finderId, $finderDef);
}
@ -520,41 +582,63 @@ class FOSElasticaExtension extends Extension
}
/**
* Loads the index manager
* Loads the index manager.
*
* @param array $indexRefsByName
* @param ContainerBuilder $container
**/
protected function loadIndexManager(array $indexRefsByName, ContainerBuilder $container)
private function loadIndexManager(ContainerBuilder $container)
{
$indexRefs = array_map(function ($index) {
return $index['reference'];
}, $this->indexConfigs);
$managerDef = $container->getDefinition('fos_elastica.index_manager');
$managerDef->replaceArgument(0, $indexRefsByName);
$managerDef->replaceArgument(1, new Reference('fos_elastica.index'));
$managerDef->replaceArgument(0, $indexRefs);
}
/**
* Loads the resetter
* Makes sure a specific driver has been loaded.
*
* @param array $indexConfigs
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param ContainerBuilder $container
* @param string $driver
*/
protected function loadResetter(array $indexConfigs, ContainerBuilder $container)
{
$resetterDef = $container->getDefinition('fos_elastica.resetter');
$resetterDef->replaceArgument(0, $indexConfigs);
}
protected function loadDriver(ContainerBuilder $container, $driver)
private function loadDriver(ContainerBuilder $container, $driver)
{
if (in_array($driver, $this->loadedDrivers)) {
return;
}
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load($driver.'.xml');
$this->loadedDrivers[] = $driver;
}
protected function createDefaultManagerAlias($defaultManager, ContainerBuilder $container)
/**
* Loads and configures the serializer prototype.
*
* @param array $config
* @param ContainerBuilder $container
*/
private function loadSerializer($config, ContainerBuilder $container)
{
$container->setAlias('fos_elastica.serializer', $config['serializer']);
$serializer = $container->getDefinition('fos_elastica.serializer_callback_prototype');
$serializer->setClass($config['callback_class']);
$callbackClassImplementedInterfaces = class_implements($config['callback_class']);
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$serializer->addMethodCall('setContainer', array(new Reference('service_container')));
}
}
/**
* Creates a default manager alias for defined default manager or the first loaded driver.
*
* @param string $defaultManager
* @param ContainerBuilder $container
*/
private function createDefaultManagerAlias($defaultManager, ContainerBuilder $container)
{
if (0 == count($this->loadedDrivers)) {
return;
@ -570,4 +654,22 @@ class FOSElasticaExtension extends Extension
$container->setAlias('fos_elastica.manager', sprintf('fos_elastica.manager.%s', $defaultManagerService));
}
/**
* Returns a reference to a client given its configured name.
*
* @param string $clientName
*
* @return Reference
*
* @throws \InvalidArgumentException
*/
private function getClient($clientName)
{
if (!array_key_exists($clientName, $this->clients)) {
throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName));
}
return $this->clients[$clientName]['reference'];
}
}

View file

@ -2,32 +2,34 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\Persistence\ManagerRegistry;
use FOS\ElasticaBundle\HybridResult;
use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use FOS\ElasticaBundle\Transformer\AbstractElasticaToModelTransformer as BaseTransformer;
use FOS\ElasticaBundle\Transformer\HighlightableModelInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
* elastica documents ids and doctrine object ids
* elastica documents ids and doctrine object ids.
*/
abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTransformerInterface
abstract class AbstractElasticaToModelTransformer extends BaseTransformer
{
/**
* Manager registry
* Manager registry.
*
* @var ManagerRegistry
*/
protected $registry = null;
/**
* Class of the model to map to the elastica documents
* Class of the model to map to the elastica documents.
*
* @var string
*/
protected $objectClass = null;
/**
* Optional parameters
* Optional parameters.
*
* @var array
*/
@ -39,20 +41,13 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
);
/**
* PropertyAccessor instance
* Instantiates a new Mapper.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Instantiates a new Mapper
*
* @param object $registry
* @param ManagerRegistry $registry
* @param string $objectClass
* @param array $options
* @param array $options
*/
public function __construct($registry, $objectClass, array $options = array())
public function __construct(ManagerRegistry $registry, $objectClass, array $options = array())
{
$this->registry = $registry;
$this->objectClass = $objectClass;
@ -69,22 +64,14 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
return $this->objectClass;
}
/**
* Set the PropertyAccessor
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Transforms an array of elastica objects into an array of
* model objects fetched from the doctrine repository
* model objects fetched from the doctrine repository.
*
* @param array $elasticaObjects of elastica objects
*
* @throws \RuntimeException
*
* @return array
**/
public function transform(array $elasticaObjects)
@ -109,29 +96,31 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
// sort objects in the order of ids
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor;
usort($objects, function($a, $b) use ($idPos, $identifier, $propertyAccessor)
{
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
});
usort($objects, $this->getSortingClosure($idPos, $identifier));
return $objects;
}
public function hybridTransform(array $elasticaObjects)
{
$indexedElasticaResults = array();
foreach ($elasticaObjects as $elasticaObject) {
$indexedElasticaResults[$elasticaObject->getId()] = $elasticaObject;
}
$objects = $this->transform($elasticaObjects);
$result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
foreach ($objects as $object) {
$id = $this->propertyAccessor->getValue($object, $this->options['identifier']);
$result[] = new HybridResult($indexedElasticaResults[$id], $object);
}
return $result;
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getIdentifierField()
{
@ -139,11 +128,12 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
}
/**
* Fetches objects by theses identifier values
* Fetches objects by theses identifier values.
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
protected abstract function findByIdentifiers(array $identifierValues, $hydrate);
abstract protected function findByIdentifiers(array $identifierValues, $hydrate);
}

View file

@ -6,84 +6,62 @@ use Doctrine\Common\Persistence\ManagerRegistry;
use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
use FOS\ElasticaBundle\Provider\IndexableInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
abstract class AbstractProvider extends BaseAbstractProvider
{
/**
* @var SliceFetcherInterface
*/
private $sliceFetcher;
/**
* @var ManagerRegistry
*/
protected $managerRegistry;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
* @param array $options
* @param array $baseOptions
* @param ManagerRegistry $managerRegistry
* @param SliceFetcherInterface $sliceFetcher
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options, $managerRegistry)
{
parent::__construct($objectPersister, $objectClass, array_merge(array(
'clear_object_manager' => true,
'ignore_errors' => false,
'query_builder_method' => 'createQueryBuilder',
), $options));
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
array $baseOptions,
ManagerRegistry $managerRegistry,
SliceFetcherInterface $sliceFetcher = null
) {
parent::__construct($objectPersister, $indexable, $objectClass, $baseOptions);
$this->managerRegistry = $managerRegistry;
}
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
{
$queryBuilder = $this->createQueryBuilder();
$nbObjects = $this->countObjects($queryBuilder);
$offset = isset($options['offset']) ? intval($options['offset']) : 0;
$sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
$ignoreErrors = isset($options['ignore-errors']) ? $options['ignore-errors'] : $this->options['ignore_errors'];
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
} else {
try {
$this->objectPersister->insertMany($objects);
} catch(BulkResponseException $e) {
if ($loggerClosure) {
$loggerClosure(sprintf('<error>%s</error>',$e->getMessage()));
}
}
}
if ($this->options['clear_object_manager']) {
$this->managerRegistry->getManagerForClass($this->objectClass)->clear();
}
usleep($sleep);
if ($loggerClosure) {
$stepNbObjects = count($objects);
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$timeDifference = microtime(true) - $stepStartTime;
$objectsPerSecond = $timeDifference ? ($stepNbObjects / $timeDifference) : $stepNbObjects;
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %s', $percentComplete, $stepCount, $nbObjects, $objectsPerSecond, $this->getMemoryUsage()));
}
}
$this->sliceFetcher = $sliceFetcher;
}
/**
* Counts objects that would be indexed using the query builder.
*
* @param object $queryBuilder
*
* @return integer
*/
protected abstract function countObjects($queryBuilder);
abstract protected function countObjects($queryBuilder);
/**
* Creates the query builder, which will be used to fetch objects to index.
*
* @param string $method
*
* @return object
*/
abstract protected function createQueryBuilder($method);
/**
* Fetches a slice of objects using the query builder.
@ -91,14 +69,102 @@ abstract class AbstractProvider extends BaseAbstractProvider
* @param object $queryBuilder
* @param integer $limit
* @param integer $offset
*
* @return array
*/
protected abstract function fetchSlice($queryBuilder, $limit, $offset);
abstract protected function fetchSlice($queryBuilder, $limit, $offset);
/**
* Creates the query builder, which will be used to fetch objects to index.
*
* @return object
* {@inheritDoc}
*/
protected abstract function createQueryBuilder();
protected function doPopulate($options, \Closure $loggerClosure = null)
{
$manager = $this->managerRegistry->getManagerForClass($this->objectClass);
$queryBuilder = $this->createQueryBuilder($options['query_builder_method']);
$nbObjects = $this->countObjects($queryBuilder);
$offset = $options['offset'];
$objects = array();
for (; $offset < $nbObjects; $offset += $options['batch_size']) {
try {
$objects = $this->getSlice($queryBuilder, $options['batch_size'], $offset, $objects);
$objects = $this->filterObjects($options, $objects);
if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
}
} catch (BulkResponseException $e) {
if (!$options['ignore_errors']) {
throw $e;
}
if (null !== $loggerClosure) {
$loggerClosure(
$options['batch_size'],
$nbObjects,
sprintf('<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,238 +2,117 @@
namespace FOS\ElasticaBundle\Doctrine;
use Psr\Log\LoggerInterface;
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 Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\IndexableInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Automatically update ElasticSearch based on changes to the Doctrine source
* data. One listener is generated for each Doctrine entity / ElasticSearch type.
*/
class Listener implements EventSubscriber
class Listener
{
/**
* Object persister
* Object persister.
*
* @var ObjectPersister
* @var ObjectPersisterInterface
*/
protected $objectPersister;
/**
* Class of the domain model
*
* @var string
*/
protected $objectClass;
/**
* List of subscribed events
* Configuration for the listener.
*
* @var array
*/
protected $events;
private $config;
/**
* Name of domain model field used as the ES identifier
* Objects scheduled for insertion.
*
* @var string
*/
protected $esIdentifierField;
/**
* Callback for determining if an object should be indexed
*
* @var mixed
*/
protected $isIndexableCallback;
/**
* Objects scheduled for insertion and replacement
* @var array
*/
public $scheduledForInsertion = array();
/**
* Objects scheduled to be updated or removed.
*
* @var array
*/
public $scheduledForUpdate = array();
/**
* IDs of objects scheduled for removal
* IDs of objects scheduled for removal.
*
* @var array
*/
public $scheduledForDeletion = array();
/**
* An instance of ExpressionLanguage
*
* @var ExpressionLanguage
*/
protected $expressionLanguage;
/**
* PropertyAccessor instance
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* @var IndexableInterface
*/
private $indexable;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param string $objectClass
* @param array $events
* @param string $esIdentifierField
* @param IndexableInterface $indexable
* @param array $config
* @param LoggerInterface $logger
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $events, $esIdentifierField = 'id', $logger = null)
{
$this->objectPersister = $objectPersister;
$this->objectClass = $objectClass;
$this->events = $events;
$this->esIdentifierField = $esIdentifierField;
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
array $config = array(),
LoggerInterface $logger = null
) {
$this->config = array_merge(array(
'identifier' => 'id',
), $config);
$this->indexable = $indexable;
$this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($logger) {
if ($logger && $this->objectPersister instanceof ObjectPersister) {
$this->objectPersister->setLogger($logger);
}
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* @see Doctrine\Common\EventSubscriber::getSubscribedEvents()
*/
public function getSubscribedEvents()
{
return $this->events;
}
/**
* Set the callback for determining object index eligibility.
* Looks for new objects that should be indexed.
*
* If callback is a string, it must be public method on the object class
* that expects no arguments and returns a boolean. Otherwise, the callback
* should expect the object for consideration as its only argument and
* return a boolean.
*
* @param callback $callback
* @throws \RuntimeException if the callback is not callable
* @param LifecycleEventArgs $eventArgs
*/
public function setIsIndexableCallback($callback)
public function postPersist(LifecycleEventArgs $eventArgs)
{
if (is_string($callback)) {
if (!is_callable(array($this->objectClass, $callback))) {
if (false !== ($expression = $this->getExpressionLanguage())) {
$callback = new Expression($callback);
try {
$expression->compile($callback, array($this->getExpressionVar()));
} catch (SyntaxError $e) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable or a valid expression.', $this->objectClass, $callback), 0, $e);
}
} else {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $this->objectClass, $callback));
}
}
} elseif (!is_callable($callback)) {
if (is_array($callback)) {
list($class, $method) = $callback + array(null, null);
if (is_object($class)) {
$class = get_class($class);
}
$entity = $eventArgs->getObject();
if ($class && $method) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $class, $method));
}
}
throw new \RuntimeException('Indexable callback is not callable.');
}
$this->isIndexableCallback = $callback;
}
/**
* Return whether the object is indexable with respect to the callback.
*
* @param object $object
* @return boolean
*/
protected function isObjectIndexable($object)
{
if (!$this->isIndexableCallback) {
return true;
}
if ($this->isIndexableCallback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($this->isIndexableCallback, array($this->getExpressionVar($object) => $object));
}
return is_string($this->isIndexableCallback)
? call_user_func(array($object, $this->isIndexableCallback))
: call_user_func($this->isIndexableCallback, $object);
}
/**
* @param mixed $object
* @return string
*/
private function getExpressionVar($object = null)
{
$class = $object ?: $this->objectClass;
$ref = new \ReflectionClass($class);
return strtolower($ref->getShortName());
}
/**
* Provides unified method for retrieving a doctrine object from an EventArgs instance
*
* @param EventArgs $eventArgs
* @return object Entity | Document
* @throws \RuntimeException if no valid getter is found.
*/
private function getDoctrineObject(EventArgs $eventArgs)
{
if (method_exists($eventArgs, 'getObject')) {
return $eventArgs->getObject();
} elseif (method_exists($eventArgs, 'getEntity')) {
return $eventArgs->getEntity();
} elseif (method_exists($eventArgs, 'getDocument')) {
return $eventArgs->getDocument();
}
throw new \RuntimeException('Unable to retrieve object from EventArgs.');
}
/**
* @return bool|ExpressionLanguage
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
public function postPersist(EventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
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 ($entity instanceof $this->objectClass) {
if ($this->objectPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate[] = $entity;
} else {
@ -245,20 +124,22 @@ class Listener implements EventSubscriber
/**
* Delete objects preRemove instead of postRemove so that we have access to the id. Because this is called
* preRemove, first check that the entity is managed by Doctrine
* preRemove, first check that the entity is managed by Doctrine.
*
* @param LifecycleEventArgs $eventArgs
*/
public function preRemove(EventArgs $eventArgs)
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
$entity = $eventArgs->getObject();
if ($entity instanceof $this->objectClass) {
if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
}
}
/**
* Persist scheduled objects to ElasticSearch
* After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls
* After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
*/
private function persistScheduled()
{
@ -277,32 +158,55 @@ class Listener implements EventSubscriber
}
/**
* Iterate through scheduled actions before flushing to emulate 2.x behavior. Note that the ElasticSearch index
* will fall out of sync with the source data in the event of a crash during flush.
* Iterate through scheduled actions before flushing to emulate 2.x behavior.
* Note that the ElasticSearch index will fall out of sync with the source
* data in the event of a crash during flush.
*
* This method is only called in legacy configurations of the listener.
*
* @deprecated This method should only be called in applications that depend
* on the behaviour that entities are indexed regardless of if a
* flush is successful.
*/
public function preFlush(EventArgs $eventArgs)
public function preFlush()
{
$this->persistScheduled();
}
/**
* Iterating through scheduled actions *after* flushing ensures that the ElasticSearch index will be affected
* only if the query is successful
* Iterating through scheduled actions *after* flushing ensures that the
* ElasticSearch index will be affected only if the query is successful.
*/
public function postFlush(EventArgs $eventArgs)
public function postFlush()
{
$this->persistScheduled();
}
/**
* Record the specified identifier to delete. Do not need to entire object.
* @param mixed $object
* @return mixed
*
* @param object $object
*/
protected function scheduleForDeletion($object)
private function scheduleForDeletion($object)
{
if ($identifierValue = $this->propertyAccessor->getValue($object, $this->esIdentifierField)) {
if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
$this->scheduledForDeletion[] = $identifierValue;
}
}
/**
* Checks if the object is indexable or not.
*
* @param object $object
*
* @return bool
*/
private function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
$this->config['indexName'],
$this->config['typeName'],
$object
);
}
}

View file

@ -7,21 +7,23 @@ use FOS\ElasticaBundle\Doctrine\AbstractElasticaToModelTransformer;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
* elastica documents ids and doctrine object ids
* elastica documents ids and doctrine object ids.
*/
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
/**
* Fetch objects for theses identifier values
* Fetch objects for theses identifier values.
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
{
return $this->registry
->getManagerForClass($this->objectClass)
->getRepository($this->objectClass)
->{$this->options['query_builder_method']}($this->objectClass)
->field($this->options['identifier'])->in($identifierValues)
->hydrate($hydrate)

View file

@ -9,7 +9,42 @@ use FOS\ElasticaBundle\Exception\InvalidArgumentTypeException;
class Provider extends AbstractProvider
{
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
* Disables logging and returns the logger that was previously set.
*
* @return mixed
*/
protected function disableLogging()
{
$configuration = $this->managerRegistry
->getManagerForClass($this->objectClass)
->getConnection()
->getConfiguration();
$logger = $configuration->getLoggerCallable();
$configuration->setLoggerCallable(null);
return $logger;
}
/**
* Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
*
* @return mixed
*/
protected function enableLogging($logger)
{
$configuration = $this->managerRegistry
->getManagerForClass($this->objectClass)
->getConnection()
->getConfiguration();
$configuration->setLoggerCallable($logger);
}
/**
* {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@ -23,7 +58,7 @@ class Provider extends AbstractProvider
}
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::fetchSlice()
* {@inheritDoc}
*/
protected function fetchSlice($queryBuilder, $limit, $offset)
{
@ -32,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

@ -8,17 +8,18 @@ use Doctrine\ORM\Query;
/**
* Maps Elastica documents with Doctrine objects
* This mapper assumes an exact match between
* elastica documents ids and doctrine object ids
* elastica documents ids and doctrine object ids.
*/
class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
{
const ENTITY_ALIAS = 'o';
/**
* Fetch objects for theses identifier values
* Fetch objects for theses identifier values.
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
*
* @param array $identifierValues ids values
* @param Boolean $hydrate whether or not to hydrate the objects, false returns arrays
* @return array of objects or arrays
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
@ -29,14 +30,14 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer
$hydrationMode = $hydrate ? Query::HYDRATE_OBJECT : Query::HYDRATE_ARRAY;
$qb = $this->getEntityQueryBuilder();
$qb->where($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values'))
$qb->andWhere($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values'))
->setParameter('values', $identifierValues);
return $qb->getQuery()->setHydrationMode($hydrationMode)->execute();
}
/**
* Retrieves a query builder to be used for querying by identifiers
* Retrieves a query builder to be used for querying by identifiers.
*
* @return \Doctrine\ORM\QueryBuilder
*/

View file

@ -11,7 +11,42 @@ class Provider extends AbstractProvider
const ENTITY_ALIAS = 'a';
/**
* @see FOS\ElasticaBundle\Doctrine\AbstractProvider::countObjects()
* Disables logging and returns the logger that was previously set.
*
* @return mixed
*/
protected function disableLogging()
{
$configuration = $this->managerRegistry
->getManagerForClass($this->objectClass)
->getConnection()
->getConfiguration();
$logger = $configuration->getSQLLogger();
$configuration->setSQLLogger(null);
return $logger;
}
/**
* Reenables the logger with the previously returned logger from disableLogging();.
*
* @param mixed $logger
*
* @return mixed
*/
protected function enableLogging($logger)
{
$configuration = $this->managerRegistry
->getManagerForClass($this->objectClass)
->getConnection()
->getConfiguration();
$configuration->setSQLLogger($logger);
}
/**
* {@inheritDoc}
*/
protected function countObjects($queryBuilder)
{
@ -34,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)
{
@ -42,8 +79,8 @@ class Provider extends AbstractProvider
throw new InvalidArgumentTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
/**
* An orderBy DQL part is required to avoid feching the same row twice.
/*
* An orderBy DQL part is required to avoid fetching the same row twice.
* @see http://stackoverflow.com/questions/6314879/does-limit-offset-length-require-order-by-for-pagination
* @see http://www.postgresql.org/docs/current/static/queries-limit.html
* @see http://www.sqlite.org/lang_select.html#orderby
@ -68,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

@ -25,7 +25,7 @@ class RepositoryManager extends BaseManager
}
/**
* Return repository for entity
* Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
@ -35,7 +35,7 @@ class RepositoryManager extends BaseManager
$realEntityName = $entityName;
if (strpos($entityName, ':') !== false) {
list($namespaceAlias, $simpleClassName) = explode(':', $entityName);
$realEntityName = $this->managerRegistry->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName;
$realEntityName = $this->managerRegistry->getAliasNamespace($namespaceAlias).'\\'.$simpleClassName;
}
return parent::getRepository($realEntityName);

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

View file

@ -2,27 +2,11 @@
namespace FOS\ElasticaBundle;
use Elastica\Index;
use FOS\ElasticaBundle\Elastica\Index;
/**
* Elastica index capable of reassigning name dynamically
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
* @deprecated Use \FOS\ElasticaBundle\Elastica\TransformingIndex
*/
class DynamicIndex extends Index
{
/**
* Reassign index name
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
*
* @return void
*/
public function overrideName($name)
{
$this->_name = $name;
}
}

104
Elastica/Client.php Normal file
View file

@ -0,0 +1,104 @@
<?php
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Client as BaseClient;
use Elastica\Request;
use FOS\ElasticaBundle\Logger\ElasticaLogger;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Extends the default Elastica client to provide logging for errors that occur
* during communication with ElasticSearch.
*
* @author Gordon Franke <info@nevalon.de>
*/
class Client extends BaseClient
{
/**
* Stores created indexes to avoid recreation.
*
* @var array
*/
private $indexCache = array();
/**
* Symfony's debugging Stopwatch.
*
* @var Stopwatch|null
*/
private $stopwatch;
/**
* @param string $path
* @param string $method
* @param array $data
* @param array $query
*
* @return \Elastica\Response
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
if ($this->stopwatch) {
$this->stopwatch->start('es_request', 'fos_elastica');
}
$start = microtime(true);
$response = parent::request($path, $method, $data, $query);
$this->logQuery($path, $method, $data, $query, $start);
if ($this->stopwatch) {
$this->stopwatch->stop('es_request');
}
return $response;
}
public function getIndex($name)
{
if (isset($this->indexCache[$name])) {
return $this->indexCache[$name];
}
return $this->indexCache[$name] = new Index($this, $name);
}
/**
* Sets a stopwatch instance for debugging purposes.
*
* @param Stopwatch $stopwatch
*/
public function setStopwatch(Stopwatch $stopwatch = null)
{
$this->stopwatch = $stopwatch;
}
/**
* Log the query if we have an instance of ElasticaLogger.
*
* @param string $path
* @param string $method
* @param array $data
* @param array $query
* @param int $start
*/
private function logQuery($path, $method, $data, array $query, $start)
{
if (!$this->_logger or !$this->_logger instanceof ElasticaLogger) {
return;
}
$time = microtime(true) - $start;
$connection = $this->getLastRequest()->getConnection();
$connection_array = array(
'host' => $connection->getHost(),
'port' => $connection->getPort(),
'transport' => $connection->getTransport(),
'headers' => $connection->hasConfig('headers') ? $connection->getConfig('headers') : array(),
);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
}
}

59
Elastica/Index.php Normal file
View file

@ -0,0 +1,59 @@
<?php
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Index as BaseIndex;
/**
* Overridden Elastica Index class that provides dynamic index name changes.
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
*/
class Index extends BaseIndex
{
private $originalName;
/**
* Stores created types to avoid recreation.
*
* @var array
*/
private $typeCache = array();
/**
* Returns the original name of the index if the index has been renamed for reindexing
* or realiasing purposes.
*
* @return string
*/
public function getOriginalName()
{
return $this->originalName ?: $this->_name;
}
/**
* @param string $type
*/
public function getType($type)
{
if (isset($this->typeCache[$type])) {
return $this->typeCache[$type];
}
return $this->typeCache[$type] = parent::getType($type);
}
/**
* Reassign index name.
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
*/
public function overrideName($name)
{
$this->originalName = $this->_name;
$this->_name = $name;
}
}

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

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

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle;
use FOS\ElasticaBundle\DependencyInjection\Compiler\ConfigSourcePass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\IndexPass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass;
use FOS\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -10,7 +12,6 @@ use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Bundle.
*
*/
class FOSElasticaBundle extends Bundle
{
@ -21,6 +22,8 @@ class FOSElasticaBundle extends Bundle
{
parent::build($container);
$container->addCompilerPass(new ConfigSourcePass());
$container->addCompilerPass(new IndexPass());
$container->addCompilerPass(new RegisterProvidersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TransformerPass());
}

View file

@ -5,12 +5,13 @@ namespace FOS\ElasticaBundle\Finder;
interface FinderInterface
{
/**
* Searches for query results within a given limit
* Searches for query results within a given limit.
*
* @param mixed $query Can be a string, an array or an \Elastica\Query object
* @param int $limit How many results to get
* @param mixed $query Can be a string, an array or an \Elastica\Query object
* @param int $limit How many results to get
* @param array $options
*
* @return array results
*/
function find($query, $limit = null, $options = array());
public function find($query, $limit = null, $options = array());
}

View file

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

View file

@ -11,7 +11,7 @@ use Elastica\SearchableInterface;
use Elastica\Query;
/**
* Finds elastica documents and map them to persisted objects
* Finds elastica documents and map them to persisted objects.
*/
class TransformedFinder implements PaginatedFinderInterface
{
@ -25,11 +25,12 @@ class TransformedFinder implements PaginatedFinderInterface
}
/**
* Search for a query string
* Search for a query string.
*
* @param string $query
* @param string $query
* @param integer $limit
* @param array $options
* @param array $options
*
* @return array of model objects
**/
public function find($query, $limit = null, $options = array())
@ -50,8 +51,9 @@ class TransformedFinder implements PaginatedFinderInterface
* Find documents similar to one with passed id.
*
* @param integer $id
* @param array $params
* @param array $query
* @param array $params
* @param array $query
*
* @return array of model objects
**/
public function moreLikeThis($id, $params = array(), $query = array())
@ -65,7 +67,8 @@ class TransformedFinder implements PaginatedFinderInterface
/**
* @param $query
* @param null|int $limit
* @param array $options
* @param array $options
*
* @return array
*/
protected function search($query, $limit = null, $options = array())
@ -80,10 +83,11 @@ class TransformedFinder implements PaginatedFinderInterface
}
/**
* Gets a paginator wrapping the result of a search
* Gets a paginator wrapping the result of a search.
*
* @param string $query
* @param array $options
* @param array $options
*
* @return Pagerfanta
*/
public function findPaginated($query, $options = array())

View file

@ -24,4 +24,4 @@ class HybridResult
{
return $this->result;
}
}
}

197
Index/AliasProcessor.php Normal file
View file

@ -0,0 +1,197 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Index;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;
use Elastica\Request;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Elastica\Index;
use FOS\ElasticaBundle\Exception\AliasIsIndexException;
class AliasProcessor
{
/**
* Sets the randomised root name for an index.
*
* @param IndexConfig $indexConfig
* @param Index $index
*/
public function setRootName(IndexConfig $indexConfig, Index $index)
{
$index->overrideName(
sprintf('%s_%s',
$indexConfig->getElasticSearchName(),
date('Y-m-d-His')
)
);
}
/**
* Switches an index to become the new target for an alias. Only applies for
* indexes that are set to use aliases.
*
* $force will delete an index encountered where an alias is expected.
*
* @param IndexConfig $indexConfig
* @param Index $index
* @param bool $force
*
* @throws AliasIsIndexException
* @throws \RuntimeException
*/
public function switchIndexAlias(IndexConfig $indexConfig, Index $index, $force = false)
{
$client = $index->getClient();
$aliasName = $indexConfig->getElasticSearchName();
$oldIndexName = null;
$newIndexName = $index->getName();
try {
$oldIndexName = $this->getAliasedIndex($client, $aliasName);
} catch (AliasIsIndexException $e) {
if (!$force) {
throw $e;
}
$this->deleteIndex($client, $aliasName);
}
try {
$aliasUpdateRequest = $this->buildAliasUpdateRequest($oldIndexName, $aliasName, $newIndexName);
$client->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $e) {
$this->cleanupRenameFailure($client, $newIndexName, $e);
}
// Delete the old index after the alias has been switched
if (null !== $oldIndexName) {
$this->deleteIndex($client, $oldIndexName);
}
}
/**
* Builds an ElasticSearch request to rename or create an alias.
*
* @param string|null $aliasedIndex
* @param string $aliasName
* @param string $newIndexName
* @return array
*/
private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexName)
{
$aliasUpdateRequest = array('actions' => array());
if (null !== $aliasedIndex) {
// if the alias is set - add an action to remove it
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $aliasedIndex, 'alias' => $aliasName),
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName),
);
return $aliasUpdateRequest;
}
/**
* Cleans up an index when we encounter a failure to rename the alias.
*
* @param Client $client
* @param string $indexName
* @param \Exception $renameAliasException
*/
private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)
{
$additionalError = '';
try {
$this->deleteIndex($client, $indexName);
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$indexName,
$deleteNewIndexException->getMessage()
);
}
throw new \RuntimeException(sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $indexName)
), 0, $renameAliasException);
}
/**
* Delete an index.
*
* @param Client $client
* @param string $indexName Index name to delete
*/
private function deleteIndex(Client $client, $indexName)
{
try {
$path = sprintf("%s", $indexName);
$client->request($path, Request::DELETE);
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(sprintf(
'Failed to delete index %s with message: %s',
$indexName,
$deleteOldIndexException->getMessage()
), 0, $deleteOldIndexException);
}
}
/**
* Returns the name of a single index that an alias points to or throws
* an exception if there is more than one.
*
* @param Client $client
* @param string $aliasName Alias name
*
* @return string|null
*
* @throws AliasIsIndexException
*/
private function getAliasedIndex(Client $client, $aliasName)
{
$aliasesInfo = $client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
if ($indexName === $aliasName) {
throw new AliasIsIndexException($indexName);
}
if (!isset($indexInfo['aliases'])) {
continue;
}
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(sprintf(
'Alias %s is used for multiple indexes: [%s]. Make sure it\'s'.
'either not used or is assigned to one index only',
$aliasName,
implode(', ', $aliasedIndexes)
));
}
return array_shift($aliasedIndexes);
}
}

70
Index/IndexManager.php Normal file
View file

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

134
Index/MappingBuilder.php Normal file
View file

@ -0,0 +1,134 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Index;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;
class MappingBuilder
{
/**
* Skip adding default information to certain fields.
*
* @var array
*/
private $skipTypes = array('completion');
/**
* Builds mappings for an entire index.
*
* @param IndexConfig $indexConfig
*
* @return array
*/
public function buildIndexMapping(IndexConfig $indexConfig)
{
$typeMappings = array();
foreach ($indexConfig->getTypes() as $typeConfig) {
$typeMappings[$typeConfig->getName()] = $this->buildTypeMapping($typeConfig);
}
$mapping = array();
if (!empty($typeMappings)) {
$mapping['mappings'] = $typeMappings;
}
// 'warmers' => $indexConfig->getWarmers(),
$settings = $indexConfig->getSettings();
if (!empty($settings)) {
$mapping['settings'] = $settings;
}
return $mapping;
}
/**
* Builds mappings for a single type.
*
* @param TypeConfig $typeConfig
*
* @return array
*/
public function buildTypeMapping(TypeConfig $typeConfig)
{
$mapping = $typeConfig->getMapping();
if (null !== $typeConfig->getDynamicDateFormats()) {
$mapping['dynamic_date_formats'] = $typeConfig->getDynamicDateFormats();
}
if (null !== $typeConfig->getDateDetection()) {
$mapping['date_detection'] = $typeConfig->getDateDetection();
}
if (null !== $typeConfig->getNumericDetection()) {
$mapping['numeric_detection'] = $typeConfig->getNumericDetection();
}
if ($typeConfig->getIndexAnalyzer()) {
$mapping['index_analyzer'] = $typeConfig->getIndexAnalyzer();
}
if ($typeConfig->getSearchAnalyzer()) {
$mapping['search_analyzer'] = $typeConfig->getSearchAnalyzer();
}
if (isset($mapping['dynamic_templates']) and empty($mapping['dynamic_templates'])) {
unset($mapping['dynamic_templates']);
}
$this->fixProperties($mapping['properties']);
if (!$mapping['properties']) {
unset($mapping['properties']);
}
if ($typeConfig->getModel()) {
$mapping['_meta']['model'] = $typeConfig->getModel();
}
if (empty($mapping)) {
// Empty mapping, we want it encoded as a {} instead of a []
$mapping = new \stdClass();
}
return $mapping;
}
/**
* Fixes any properties and applies basic defaults for any field that does not have
* required options.
*
* @param $properties
*/
private function fixProperties(&$properties)
{
foreach ($properties as $name => &$property) {
unset($property['property_path']);
if (!isset($property['type'])) {
$property['type'] = 'string';
}
if ($property['type'] == 'multi_field' && isset($property['fields'])) {
$this->fixProperties($property['fields']);
}
if (isset($property['properties'])) {
$this->fixProperties($property['properties']);
}
if (in_array($property['type'], $this->skipTypes)) {
continue;
}
if (!isset($property['store'])) {
$property['store'] = true;
}
}
}
}

158
Index/Resetter.php Normal file
View file

@ -0,0 +1,158 @@
<?php
namespace FOS\ElasticaBundle\Index;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\ConfigManager;
use FOS\ElasticaBundle\Event\IndexResetEvent;
use FOS\ElasticaBundle\Event\TypeResetEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Deletes and recreates indexes.
*/
class Resetter
{
/**
* @var AliasProcessor
*/
private $aliasProcessor;
/***
* @var ConfigManager
*/
private $configManager;
/**
* @var EventDispatcherInterface
*/
private $dispatcher;
/**
* @var IndexManager
*/
private $indexManager;
/**
* @var MappingBuilder
*/
private $mappingBuilder;
/**
* @param ConfigManager $configManager
* @param IndexManager $indexManager
* @param AliasProcessor $aliasProcessor
* @param MappingBuilder $mappingBuilder
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct(
ConfigManager $configManager,
IndexManager $indexManager,
AliasProcessor $aliasProcessor,
MappingBuilder $mappingBuilder,
EventDispatcherInterface $eventDispatcher
) {
$this->aliasProcessor = $aliasProcessor;
$this->configManager = $configManager;
$this->dispatcher = $eventDispatcher;
$this->indexManager = $indexManager;
$this->mappingBuilder = $mappingBuilder;
}
/**
* Deletes and recreates all indexes.
*
* @param bool $populating
* @param bool $force
*/
public function resetAllIndexes($populating = false, $force = false)
{
foreach ($this->configManager->getIndexNames() as $name) {
$this->resetIndex($name, $populating, $force);
}
}
/**
* Deletes and recreates the named index. If populating, creates a new index
* with a randomised name for an alias to be set after population.
*
* @param string $indexName
* @param bool $populating
* @param bool $force If index exists with same name as alias, remove it
*
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function resetIndex($indexName, $populating = false, $force = false)
{
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
$index = $this->indexManager->getIndex($indexName);
$event = new IndexResetEvent($indexName, $populating, $force);
$this->dispatcher->dispatch(IndexResetEvent::PRE_INDEX_RESET, $event);
if ($indexConfig->isUseAlias()) {
$this->aliasProcessor->setRootName($indexConfig, $index);
}
$mapping = $this->mappingBuilder->buildIndexMapping($indexConfig);
$index->create($mapping, true);
if (!$populating and $indexConfig->isUseAlias()) {
$this->aliasProcessor->switchIndexAlias($indexConfig, $index, $force);
}
$this->dispatcher->dispatch(IndexResetEvent::POST_INDEX_RESET, $event);
}
/**
* Deletes and recreates a mapping type for the named index.
*
* @param string $indexName
* @param string $typeName
*
* @throws \InvalidArgumentException if no index or type mapping exists for the given names
* @throws ResponseException
*/
public function resetIndexType($indexName, $typeName)
{
$typeConfig = $this->configManager->getTypeConfiguration($indexName, $typeName);
$type = $this->indexManager->getIndex($indexName)->getType($typeName);
$event = new TypeResetEvent($indexName, $typeName);
$this->dispatcher->dispatch(TypeResetEvent::PRE_TYPE_RESET, $event);
try {
$type->delete();
} catch (ResponseException $e) {
if (strpos($e->getMessage(), 'TypeMissingException') === false) {
throw $e;
}
}
$mapping = new Mapping();
foreach ($this->mappingBuilder->buildTypeMapping($typeConfig) as $name => $field) {
$mapping->setParam($name, $field);
}
$type->setMapping($mapping);
$this->dispatcher->dispatch(TypeResetEvent::POST_TYPE_RESET, $event);
}
/**
* A command run when a population has finished.
*
* @param string $indexName
*/
public function postPopulate($indexName)
{
$indexConfig = $this->configManager->getIndexConfiguration($indexName);
if ($indexConfig->isUseAlias()) {
$index = $this->indexManager->getIndex($indexName);
$this->aliasProcessor->switchIndexAlias($indexConfig, $index);
}
}
}

View file

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

View file

@ -25,13 +25,13 @@ class RepositoryManager implements RepositoryManagerInterface
public function addEntity($entityName, FinderInterface $finder, $repositoryName = null)
{
$this->entities[$entityName]= array();
$this->entities[$entityName] = array();
$this->entities[$entityName]['finder'] = $finder;
$this->entities[$entityName]['repositoryName'] = $repositoryName;
}
/**
* Return repository for entity
* Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.
@ -59,16 +59,20 @@ class RepositoryManager implements RepositoryManagerInterface
}
$refClass = new \ReflectionClass($entityName);
$annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Configuration\\Search');
$annotation = $this->reader->getClassAnnotation($refClass, 'FOS\\ElasticaBundle\\Annotation\\Search');
if ($annotation) {
$this->entities[$entityName]['repositoryName']
= $annotation->repositoryClass;
return $annotation->repositoryClass;
}
return 'FOS\ElasticaBundle\Repository';
}
/**
* @param string $entityName
*/
private function createRepository($entityName)
{
if (!class_exists($repositoryName = $this->getRepositoryName($entityName))) {

View file

@ -12,7 +12,6 @@ use FOS\ElasticaBundle\Finder\FinderInterface;
*/
interface RepositoryManagerInterface
{
/**
* Adds entity name and its finder.
* Custom repository class name can also be added.
@ -24,7 +23,7 @@ interface RepositoryManagerInterface
public function addEntity($entityName, FinderInterface $finder, $repositoryName = null);
/**
* Return repository for entity
* Return repository for entity.
*
* Returns custom repository if one specified otherwise
* returns a basic repository.

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()
{
@ -29,15 +27,25 @@ class FantaPaginatorAdapter implements AdapterInterface
}
/**
* Returns Facets
* Returns Facets.
*
* @return mixed
*/
public function getFacets()
{
return $this->adapter->getFacets();
}
/**
* Returns Aggregations.
*
* @return mixed
*
* @api
*/
public function getFacets()
public function getAggregations()
{
return $this->adapter->getFacets();
return $this->adapter->getAggregations();
}
/**
@ -47,8 +55,6 @@ class FantaPaginatorAdapter implements AdapterInterface
* @param integer $length The length.
*
* @return array|\Traversable The slice.
*
* @api
*/
public function getSlice($offset, $length)
{

View file

@ -8,10 +8,8 @@ interface PaginatorAdapterInterface
* Returns the number of results.
*
* @return integer The number of results.
*
* @api
*/
function getTotalHits();
public function getTotalHits();
/**
* Returns an slice of the results.
@ -20,15 +18,20 @@ interface PaginatorAdapterInterface
* @param integer $length The length.
*
* @return PartialResultsInterface
*
* @api
*/
function getResults($offset, $length);
public function getResults($offset, $length);
/**
* Returns Facets
* Returns Facets.
*
* @return mixed
*/
function getFacets();
public function getFacets();
/**
* Returns Aggregations.
*
* @return mixed
*/
public function getAggregations();
}

View file

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

View file

@ -8,7 +8,7 @@ use Elastica\ResultSet;
use InvalidArgumentException;
/**
* Allows pagination of Elastica\Query. Does not map results
* Allows pagination of Elastica\Query. Does not map results.
*/
class RawPaginatorAdapter implements PaginatorAdapterInterface
{
@ -37,11 +37,16 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*/
private $facets;
/**
* @var array for the aggregations
*/
private $aggregations;
/**
* @see PaginatorAdapterInterface::__construct
*
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param Query $query the query to search
* @param array $options
*/
public function __construct(SearchableInterface $searchable, Query $query, array $options = array())
@ -54,9 +59,11 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the paginated results.
*
* @param $offset
* @param $itemCountPerPage
* @param integer $offset
* @param integer $itemCountPerPage
*
* @throws \InvalidArgumentException
*
* @return ResultSet
*/
protected function getElasticaResults($offset, $itemCountPerPage)
@ -67,7 +74,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
? (integer) $this->query->getParam('size')
: null;
if ($size && $size < $offset + $itemCountPerPage) {
if (null !== $size && $size < $offset + $itemCountPerPage) {
$itemCountPerPage = $size - $offset;
}
@ -82,6 +89,8 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
$resultSet = $this->searchable->search($query, $this->options);
$this->totalHits = $resultSet->getTotalHits();
$this->facets = $resultSet->getFacets();
$this->aggregations = $resultSet->getAggregations();
return $resultSet;
}
@ -90,6 +99,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
*
* @param int $offset
* @param int $itemCountPerPage
*
* @return PartialResultsInterface
*/
public function getResults($offset, $itemCountPerPage)
@ -100,27 +110,33 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
/**
* Returns the number of results.
*
* If genuineTotal is provided as true, total hits is returned from the
* hits.total value from the search results instead of just returning
* the requested size.
*
* @param boolean $genuineTotal
*
* @return integer The number of results.
*/
public function getTotalHits()
public function getTotalHits($genuineTotal = false)
{
if ( ! isset($this->totalHits)) {
$this->totalHits = $this->searchable->search($this->query)->getTotalHits();
if (! isset($this->totalHits)) {
$this->totalHits = $this->searchable->count($this->query);
}
return $this->query->hasParam('size')
return $this->query->hasParam('size') && !$genuineTotal
? min($this->totalHits, (integer) $this->query->getParam('size'))
: $this->totalHits;
}
/**
* Returns Facets
* Returns Facets.
*
* @return mixed
*/
public function getFacets()
{
if ( ! isset($this->facets)) {
if (! isset($this->facets)) {
$this->facets = $this->searchable->search($this->query)->getFacets();
}
@ -128,7 +144,21 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
}
/**
* Returns the Query
* Returns Aggregations.
*
* @return mixed
*/
public function getAggregations()
{
if (!isset($this->aggregations)) {
$this->aggregations = $this->searchable->search($this->query)->getAggregations();
}
return $this->aggregations;
}
/**
* Returns the Query.
*
* @return Query the search query
*/

View file

@ -6,7 +6,7 @@ use Elastica\ResultSet;
use Elastica\Result;
/**
* Raw partial results transforms to a simple array
* Raw partial results transforms to a simple array.
*/
class RawPartialResults implements PartialResultsInterface
{
@ -25,7 +25,7 @@ class RawPartialResults implements PartialResultsInterface
*/
public function toArray()
{
return array_map(function(Result $result) {
return array_map(function (Result $result) {
return $result->getSource();
}, $this->resultSet->getResults());
}
@ -47,6 +47,18 @@ class RawPartialResults implements PartialResultsInterface
return $this->resultSet->getFacets();
}
return null;
return;
}
}
/**
* {@inheritDoc}
*/
public function getAggregations()
{
if ($this->resultSet->hasAggregations()) {
return $this->resultSet->getAggregations();
}
return;
}
}

View file

@ -7,15 +7,15 @@ use Elastica\SearchableInterface;
use Elastica\Query;
/**
* Allows pagination of \Elastica\Query
* Allows pagination of \Elastica\Query.
*/
class TransformedPaginatorAdapter extends RawPaginatorAdapter
{
private $transformer;
/**
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param SearchableInterface $searchable the object to search in
* @param Query $query the query to search
* @param array $options
* @param ElasticaToModelTransformerInterface $transformer the transformer for fetching the results
*/

View file

@ -6,14 +6,14 @@ use FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface;
use Elastica\ResultSet;
/**
* Partial transformed result set
* Partial transformed result set.
*/
class TransformedPartialResults extends RawPartialResults
{
protected $transformer;
/**
* @param ResultSet $resultSet
* @param ResultSet $resultSet
* @param \FOS\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface $transformer
*/
public function __construct(ResultSet $resultSet, ElasticaToModelTransformerInterface $transformer)
@ -30,4 +30,4 @@ class TransformedPartialResults extends RawPartialResults
{
return $this->transformer->transform($this->resultSet->getResults());
}
}
}

View file

@ -4,14 +4,13 @@ namespace FOS\ElasticaBundle\Persister;
use Psr\Log\LoggerInterface;
use Elastica\Exception\BulkException;
use Elastica\Exception\NotFoundException;
use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
use Elastica\Type;
use Elastica\Document;
/**
* Inserts, replaces and deletes single documents in an elastica type
* Accepts domain model objects and converts them to elastica documents
* Accepts domain model objects and converts them to elastica documents.
*
* @author Thibault Duplessis <thibault.duplessis@gmail.com>
*/
@ -31,17 +30,32 @@ class ObjectPersister implements ObjectPersisterInterface
$this->fields = $fields;
}
/**
* If the ObjectPersister handles a given object.
*
* @param object $object
*
* @return bool
*/
public function handlesObject($object)
{
return $object instanceof $this->objectClass;
}
/**
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Log exception if logger defined for persister belonging to the current listener, otherwise re-throw
* Log exception if logger defined for persister belonging to the current listener, otherwise re-throw.
*
* @param BulkException $e
*
* @throws BulkException
* @return null
*/
private function log(BulkException $e)
{
@ -54,61 +68,47 @@ class ObjectPersister implements ObjectPersisterInterface
/**
* Insert one object into the type
* The object will be transformed to an elastica document
* The object will be transformed to an elastica document.
*
* @param object $object
*/
public function insertOne($object)
{
$document = $this->transformToElasticaDocument($object);
$this->type->addDocument($document);
$this->insertMany(array($object));
}
/**
* Replaces one object in the type
* Replaces one object in the type.
*
* @param object $object
* @return null
**/
public function replaceOne($object)
{
$document = $this->transformToElasticaDocument($object);
try {
$this->type->deleteById($document->getId());
} catch (NotFoundException $e) {}
$this->type->addDocument($document);
$this->replaceMany(array($object));
}
/**
* Deletes one object in the type
* Deletes one object in the type.
*
* @param object $object
* @return null
**/
public function deleteOne($object)
{
$document = $this->transformToElasticaDocument($object);
try {
$this->type->deleteById($document->getId());
} catch (NotFoundException $e) {}
$this->deleteMany(array($object));
}
/**
* Deletes one object in the type by id
* Deletes one object in the type by id.
*
* @param mixed $id
*
* @return null
**/
public function deleteById($id)
{
try {
$this->type->deleteById($id);
} catch (NotFoundException $e) {}
$this->deleteManyByIdentifiers(array($id));
}
/**
* Bulk insert an array of objects in the type for the given method
* Bulk insert an array of objects in the type for the given method.
*
* @param array $objects array of domain model objects
* @param string Method to call
@ -148,7 +148,7 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
* Bulk deletes an array of objects in the type
* Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
@ -166,7 +166,7 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
* Bulk deletes records from an array of identifiers
* Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/
@ -180,9 +180,10 @@ class ObjectPersister implements ObjectPersisterInterface
}
/**
* Transforms an object to an elastica document
* Transforms an object to an elastica document.
*
* @param object $object
*
* @return Document the elastica document
*/
public function transformToElasticaDocument($object)

View file

@ -4,66 +4,73 @@ namespace FOS\ElasticaBundle\Persister;
/**
* Inserts, replaces and deletes single documents in an elastica type
* Accepts domain model objects and converts them to elastica documents
* Accepts domain model objects and converts them to elastica documents.
*
* @author Thibault Duplessis <thibault.duplessis@gmail.com>
*/
interface ObjectPersisterInterface
{
/**
* Checks if this persister can handle the given object or not.
*
* @param mixed $object
*
* @return boolean
*/
public function handlesObject($object);
/**
* Insert one object into the type
* The object will be transformed to an elastica document
* The object will be transformed to an elastica document.
*
* @param object $object
*/
function insertOne($object);
public function insertOne($object);
/**
* Replaces one object in the type
* Replaces one object in the type.
*
* @param object $object
**/
function replaceOne($object);
public function replaceOne($object);
/**
* Deletes one object in the type
* Deletes one object in the type.
*
* @param object $object
**/
function deleteOne($object);
public function deleteOne($object);
/**
* Deletes one object in the type by id
* Deletes one object in the type by id.
*
* @param mixed $id
*
* @return null
*/
function deleteById($id);
public function deleteById($id);
/**
* Bulk inserts an array of objects in the type
* Bulk inserts an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
function insertMany(array $objects);
public function insertMany(array $objects);
/**
* Bulk updates an array of objects in the type
* Bulk updates an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
function replaceMany(array $objects);
public function replaceMany(array $objects);
/**
* Bulk deletes an array of objects in the type
* Bulk deletes an array of objects in the type.
*
* @param array $objects array of domain model objects
*/
function deleteMany(array $objects);
public function deleteMany(array $objects);
/**
* Bulk deletes records from an array of identifiers
* Bulk deletes records from an array of identifiers.
*
* @param array $identifiers array of domain model object identifiers
*/

View file

@ -9,7 +9,7 @@ use FOS\ElasticaBundle\Transformer\ModelToElasticaTransformerInterface;
/**
* Inserts, replaces and deletes single objects in an elastica type, making use
* of elastica's serializer support to convert objects in to elastica documents.
* Accepts domain model objects and passes them directly to elastica
* Accepts domain model objects and passes them directly to elastica.
*
* @author Lea Haensenberber <lea.haensenberger@gmail.com>
*/
@ -17,17 +17,25 @@ class ObjectSerializerPersister extends ObjectPersister
{
protected $serializer;
/**
* @param Type $type
* @param ModelToElasticaTransformerInterface $transformer
* @param string $objectClass
* @param callable $serializer
*/
public function __construct(Type $type, ModelToElasticaTransformerInterface $transformer, $objectClass, $serializer)
{
parent::__construct($type, $transformer, $objectClass, array());
$this->serializer = $serializer;
$this->serializer = $serializer;
}
/**
* Transforms an object to an elastica document
* with just the identifier set
* with just the identifier set.
*
* @param object $object
*
* @return Document the elastica document
*/
public function transformToElasticaDocument($object)

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,18 +34,11 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
'identifier' => 'id',
);
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* Constructor.
*
* @param string $objectClass
* @param array $options
* @param array $options
*/
public function __construct($objectClass, array $options = array())
{
@ -52,21 +46,12 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$this->options = array_merge($this->options, $options);
}
/**
* Set the PropertyAccessor instance.
*
* @param PropertyAccessorInterface $propertyAccessor
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* Transforms an array of Elastica document into an array of Propel entities
* fetched from the database.
*
* @param array $elasticaObjects
*
* @return array|\ArrayObject
*/
public function transform(array $elasticaObjects)
@ -81,11 +66,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
// Sort objects in the order of their IDs
$idPos = array_flip($ids);
$identifier = $this->options['identifier'];
$propertyAccessor = $this->propertyAccessor;
$sortCallback = function($a, $b) use ($idPos, $identifier, $propertyAccessor) {
return $idPos[$propertyAccessor->getValue($a, $identifier)] > $idPos[$propertyAccessor->getValue($b, $identifier)];
};
$sortCallback = $this->getSortingClosure($idPos, $identifier);
if (is_object($objects)) {
$objects->uasort($sortCallback);
@ -104,7 +85,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$objects = $this->transform($elasticaObjects);
$result = array();
for ($i = 0; $i < count($elasticaObjects); $i++) {
for ($i = 0, $j = count($elasticaObjects); $i < $j; $i++) {
$result[] = new HybridResult($elasticaObjects[$i], $objects[$i]);
}
@ -135,6 +116,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
*
* @param array $identifierValues Identifier values
* @param boolean $hydrate Whether or not to hydrate the results
*
* @return array
*/
protected function findByIdentifiers(array $identifierValues, $hydrate)
@ -145,7 +127,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
$query = $this->createQuery($this->objectClass, $this->options['identifier'], $identifierValues);
if ( ! $hydrate) {
if (! $hydrate) {
return $query->toArray();
}
@ -158,6 +140,7 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
* @param string $class Propel model class
* @param string $identifierField Identifier field name (e.g. "id")
* @param array $identifierValues Identifier values
*
* @return \ModelCriteria
*/
protected function createQuery($class, $identifierField, array $identifierValues)
@ -170,6 +153,8 @@ class ElasticaToModelTransformer implements ElasticaToModelTransformerInterface
/**
* @see https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Util/Inflector.php
*
* @param string $str
*/
private function camelize($str)
{

View file

@ -5,44 +5,69 @@ namespace FOS\ElasticaBundle\Propel;
use FOS\ElasticaBundle\Provider\AbstractProvider;
/**
* Propel provider
* Propel provider.
*
* @author William Durand <william.durand1@gmail.com>
*/
class Provider extends AbstractProvider
{
/**
* @see FOS\ElasticaBundle\Provider\ProviderInterface::populate()
* {@inheritDoc}
*/
public function populate(\Closure $loggerClosure = null, array $options = array())
public function doPopulate($options, \Closure $loggerClosure = null)
{
$queryClass = $this->objectClass . 'Query';
$queryClass = $this->objectClass.'Query';
$nbObjects = $queryClass::create()->count();
$offset = isset($options['offset']) ? intval($options['offset']) : 0;
$sleep = isset($options['sleep']) ? intval($options['sleep']) : 0;
$batchSize = isset($options['batch-size']) ? intval($options['batch-size']) : $this->options['batch_size'];
for (; $offset < $nbObjects; $offset += $batchSize) {
if ($loggerClosure) {
$stepStartTime = microtime(true);
$offset = $options['offset'];
for (; $offset < $nbObjects; $offset += $options['batch_size']) {
$objects = $queryClass::create()
->limit($options['batch_size'])
->offset($offset)
->find()
->getArrayCopy();
$objects = $this->filterObjects($options, $objects);
if (!empty($objects)) {
$this->objectPersister->insertMany($objects);
}
$objects = $queryClass::create()
->limit($batchSize)
->offset($offset)
->find();
$this->objectPersister->insertMany($objects->getArrayCopy());
usleep($sleep);
usleep($options['sleep']);
if ($loggerClosure) {
$stepNbObjects = count($objects);
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
$loggerClosure(sprintf('%0.1f%% (%d/%d), %d objects/s %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,16 +3,17 @@
namespace FOS\ElasticaBundle\Provider;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* AbstractProvider
* AbstractProvider.
*/
abstract class AbstractProvider implements ProviderInterface
{
/**
* @var ObjectPersisterInterface
* @var array
*/
protected $objectPersister;
protected $baseOptions;
/**
* @var string
@ -20,29 +21,154 @@ abstract class AbstractProvider implements ProviderInterface
protected $objectClass;
/**
* @var array
* @var ObjectPersisterInterface
*/
protected $options;
protected $objectPersister;
/**
* @var OptionsResolver
*/
protected $resolver;
/**
* @var IndexableInterface
*/
private $indexable;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param IndexableInterface $indexable
* @param string $objectClass
* @param array $options
* @param array $baseOptions
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options = array())
{
$this->objectPersister = $objectPersister;
public function __construct(
ObjectPersisterInterface $objectPersister,
IndexableInterface $indexable,
$objectClass,
array $baseOptions = array()
) {
$this->baseOptions = $baseOptions;
$this->indexable = $indexable;
$this->objectClass = $objectClass;
$this->options = array_merge(array(
'batch_size' => 100,
), $options);
$this->objectPersister = $objectPersister;
$this->resolver = new OptionsResolver();
$this->configureOptions();
}
/**
* Get string with RAM usage information (current and peak)
* {@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,
'skip_indexable_check' => false,
));
$this->resolver->setAllowedTypes(array(
'batch_size' => 'int'
));
$this->resolver->setRequired(array(
'indexName',
'typeName',
));
}
/**
* Filters objects away if they are not indexable.
*
* @param array $options
* @param array $objects
* @return array
*/
protected function filterObjects(array $options, array $objects)
{
if ($options['skip_indexable_check']) {
return $objects;
}
$index = $options['indexName'];
$type = $options['typeName'];
$return = array();
foreach ($objects as $object) {
if (!$this->indexable->isObjectIndexable($index, $type, $object)) {
continue;
}
$return[] = $object;
}
return $return;
}
/**
* Checks if a given object should be indexed or not.
*
* @deprecated To be removed in 4.0
*
* @param object $object
*
* @return bool
*/
protected function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
$this->baseOptions['indexName'],
$this->baseOptions['typeName'],
$object
);
}
/**
* Get string with RAM usage information (current and peak).
*
* @deprecated To be removed in 4.0
*
* @return string
*/
@ -53,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));
}
}

240
Provider/Indexable.php Normal file
View file

@ -0,0 +1,240 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Provider;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class Indexable implements IndexableInterface
{
/**
* An array of raw configured callbacks for all types.
*
* @var array
*/
private $callbacks = array();
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
/**
* An instance of ExpressionLanguage.
*
* @var ExpressionLanguage
*/
private $expressionLanguage;
/**
* An array of initialised callbacks.
*
* @var array
*/
private $initialisedCallbacks = array();
/**
* PropertyAccessor instance.
*
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param array $callbacks
* @param ContainerInterface $container
*/
public function __construct(array $callbacks, ContainerInterface $container)
{
$this->callbacks = $callbacks;
$this->container = $container;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* Return whether the object is indexable with respect to the callback.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
*
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object)
{
$type = sprintf('%s/%s', $indexName, $typeName);
$callback = $this->getCallback($type, $object);
if (!$callback) {
return true;
}
if ($callback instanceof Expression) {
return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
$this->getExpressionVar($object) => $object,
));
}
return is_string($callback)
? call_user_func(array($object, $callback))
: call_user_func($callback, $object);
}
/**
* Builds and initialises a callback.
*
* @param string $type
* @param object $object
*
* @return mixed
*/
private function buildCallback($type, $object)
{
if (!array_key_exists($type, $this->callbacks)) {
return;
}
$callback = $this->callbacks[$type];
if (is_callable($callback) or is_callable(array($object, $callback))) {
return $callback;
}
if (is_array($callback) && !is_object($callback[0])) {
return $this->processArrayToCallback($type, $callback);
}
if (is_string($callback)) {
return $this->buildExpressionCallback($type, $object, $callback);
}
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
}
/**
* Processes a string expression into an Expression.
*
* @param string $type
* @param mixed $object
* @param string $callback
*
* @return Expression
*/
private function buildExpressionCallback($type, $object, $callback)
{
$expression = $this->getExpressionLanguage();
if (!$expression) {
throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
}
try {
$callback = new Expression($callback);
$expression->compile($callback, array(
'object', $this->getExpressionVar($object)
));
return $callback;
} catch (SyntaxError $e) {
throw new \InvalidArgumentException(sprintf(
'Callback for type "%s" is an invalid expression',
$type
), $e->getCode(), $e);
}
}
/**
* Retreives a cached callback, or creates a new callback if one is not found.
*
* @param string $type
* @param object $object
*
* @return mixed
*/
private function getCallback($type, $object)
{
if (!array_key_exists($type, $this->initialisedCallbacks)) {
$this->initialisedCallbacks[$type] = $this->buildCallback($type, $object);
}
return $this->initialisedCallbacks[$type];
}
/**
* Returns the ExpressionLanguage class if it is available.
*
* @return ExpressionLanguage|null
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
/**
* Returns the variable name to be used to access the object when using the ExpressionLanguage
* component to parse and evaluate an expression.
*
* @param mixed $object
*
* @return string
*/
private function getExpressionVar($object = null)
{
if (!is_object($object)) {
return 'object';
}
$ref = new \ReflectionClass($object);
return strtolower($ref->getShortName());
}
/**
* Processes an array into a callback. Replaces the first element with a service if
* it begins with an @.
*
* @param string $type
* @param array $callback
* @return array
*/
private function processArrayToCallback($type, array $callback)
{
list($class, $method) = $callback + array(null, '__invoke');
if (strpos($class, '@') === 0) {
$service = $this->container->get(substr($class, 1));
$callback = array($service, $method);
if (!is_callable($callback)) {
throw new \InvalidArgumentException(sprintf(
'Method "%s" on service "%s" is not callable.',
$method,
substr($class, 1)
));
}
return $callback;
}
throw new \InvalidArgumentException(sprintf(
'Unable to parse callback array for type "%s"',
$type
));
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Provider;
interface IndexableInterface
{
/**
* Checks if an object passed should be indexable or not.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
*
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object);
}

View file

@ -3,7 +3,7 @@
namespace FOS\ElasticaBundle\Provider;
/**
* Insert application domain objects into elastica types
* Insert application domain objects into elastica types.
*
* @author Thibault Duplessis <thibault.duplessis@gmail.com>
*/
@ -12,9 +12,15 @@ interface ProviderInterface
/**
* Persists all domain objects to ElasticSearch for this provider.
*
* The closure can expect 2 or 3 arguments:
* * The step size
* * The total number of objects
* * A message to output in error conditions (not normally provided)
*
* @param \Closure $loggerClosure
* @param array $options
*
* @return
*/
function populate(\Closure $loggerClosure = null, array $options = array());
public function populate(\Closure $loggerClosure = null, array $options = array());
}

View file

@ -57,8 +57,10 @@ class ProviderRegistry implements ContainerAwareInterface
*
* Providers will be indexed by "type" strings in the returned array.
*
* @param string $index
* @return array of ProviderInterface instances
* @param string $index
*
* @return ProviderInterface[]
*
* @throws \InvalidArgumentException if no providers were registered for the index
*/
public function getIndexProviders($index)
@ -81,7 +83,9 @@ class ProviderRegistry implements ContainerAwareInterface
*
* @param string $index
* @param string $type
*
* @return ProviderInterface
*
* @throws \InvalidArgumentException if no provider was registered for the index and type
*/
public function getProvider($index, $type)

View file

@ -10,16 +10,17 @@ 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)
[![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

@ -19,21 +19,47 @@ class Repository
$this->finder = $finder;
}
/**
* @param mixed $query
* @param integer $limit
* @param array $options
*
* @return array
*/
public function find($query, $limit = null, $options = array())
{
return $this->finder->find($query, $limit, $options);
}
/**
* @param mixed $query
* @param integer $limit
* @param array $options
*
* @return mixed
*/
public function findHybrid($query, $limit = null, $options = array())
{
return $this->finder->findHybrid($query, $limit, $options);
}
/**
* @param mixed $query
* @param array $options
*
* @return \Pagerfanta\Pagerfanta
*/
public function findPaginated($query, $options = array())
{
return $this->finder->findPaginated($query, $options);
}
/**
* @param string $query
* @param array $options
*
* @return Paginator\PaginatorAdapterInterface
*/
public function createPaginatorAdapter($query, $options = array())
{
return $this->finder->createPaginatorAdapter($query, $options);

View file

@ -2,245 +2,11 @@
namespace FOS\ElasticaBundle;
use Elastica\Exception\ExceptionInterface;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Index\Resetter as BaseResetter;
/**
* Deletes and recreates indexes
* @deprecated Use \FOS\ElasticaBundle\Index\Resetter
*/
class Resetter
class Resetter extends BaseResetter
{
protected $indexConfigsByName;
/**
* Constructor.
*
* @param array $indexConfigsByName
*/
public function __construct(array $indexConfigsByName)
{
$this->indexConfigsByName = $indexConfigsByName;
}
/**
* Deletes and recreates all indexes
*/
public function resetAllIndexes()
{
foreach (array_keys($this->indexConfigsByName) as $name) {
$this->resetIndex($name);
}
}
/**
* Deletes and recreates the named index
*
* @param string $indexName
* @throws \InvalidArgumentException if no index exists for the given name
*/
public function resetIndex($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$esIndex = $indexConfig['index'];
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$name = $indexConfig['name_or_alias'];
$name .= uniqid();
$esIndex->overrideName($name);
$esIndex->create($indexConfig['config']);
return;
}
$esIndex->create($indexConfig['config'], true);
}
/**
* Deletes and recreates a mapping type for the named index
*
* @param string $indexName
* @param string $typeName
* @throws \InvalidArgumentException if no index or type mapping exists for the given names
* @throws ResponseException
*/
public function resetIndexType($indexName, $typeName)
{
$indexConfig = $this->getIndexConfig($indexName);
if (!isset($indexConfig['config']['mappings'][$typeName]['properties'])) {
throw new \InvalidArgumentException(sprintf('The mapping for index "%s" and type "%s" does not exist.', $indexName, $typeName));
}
$type = $indexConfig['index']->getType($typeName);
try {
$type->delete();
} catch (ResponseException $e) {
if (strpos($e->getMessage(), 'TypeMissingException') === false) {
throw $e;
}
}
$mapping = $this->createMapping($indexConfig['config']['mappings'][$typeName]);
$type->setMapping($mapping);
}
/**
* create type mapping object
*
* @param array $indexConfig
* @return Mapping
*/
protected function createMapping($indexConfig)
{
$mapping = Mapping::create($indexConfig['properties']);
$mappingSpecialFields = array('_uid', '_id', '_source', '_all', '_analyzer', '_boost', '_routing', '_index', '_size', '_timestamp', '_ttl', 'dynamic_templates');
foreach ($mappingSpecialFields as $specialField) {
if (isset($indexConfig[$specialField])) {
$mapping->setParam($specialField, $indexConfig[$specialField]);
}
}
if (isset($indexConfig['_parent'])) {
$mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type']));
}
return $mapping;
}
/**
* Gets an index config by its name
*
* @param string $indexName Index name
*
* @param $indexName
* @return array
* @throws \InvalidArgumentException if no index config exists for the given name
*/
protected function getIndexConfig($indexName)
{
if (!isset($this->indexConfigsByName[$indexName])) {
throw new \InvalidArgumentException(sprintf('The configuration for index "%s" does not exist.', $indexName));
}
return $this->indexConfigsByName[$indexName];
}
public function postPopulate($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$this->switchIndexAlias($indexName);
}
}
/**
* Switches the alias for given index (by key) to the newly populated index
* and deletes the old index
*
* @param string $indexName Index name
*
* @throws \RuntimeException
*/
private function switchIndexAlias($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$esIndex = $indexConfig['index'];
$aliasName = $indexConfig['name_or_alias'];
$oldIndexName = false;
$newIndexName = $esIndex->getName();
$aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName);
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(
sprintf(
'Alias %s is used for multiple indexes: [%s].
Make sure it\'s either not used or is assigned to one index only',
$aliasName,
join(', ', $aliasedIndexes)
)
);
}
// Change the alias to point to the new index
// Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic
// In 0.20.x it's atomic, but it doesn't return the old index name
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) == 1) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName)
);
try {
$esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$esIndex->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getError()
);
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
)
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($esIndex->getClient(), $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
)
);
}
}
}
/**
* Returns array of indexes which are mapped to given alias
*
* @param Index $esIndex ES Index
* @param string $aliasName Alias name
*
* @return array
*/
private function getAliasedIndexes(Index $esIndex, $aliasName)
{
$aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
return $aliasedIndexes;
}
}

View file

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

View file

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

View file

@ -4,23 +4,35 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<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>
<service id="fos_elastica.provider.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true">
<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\ElasticaBundle\Doctrine\Listener" public="false">
<service id="fos_elastica.listener.prototype.mongodb" class="%fos_elastica.listener.prototype.mongodb.class%" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument/> <!-- identifier -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false">
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="%fos_elastica.elastica_to_model_transformer.prototype.mongodb.class%" public="false" abstract="true">
<argument type="service" id="doctrine_mongodb" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
@ -29,11 +41,9 @@
</call>
</service>
<service id="fos_elastica.manager.mongodb" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<service id="fos_elastica.manager.mongodb" class="%fos_elastica.manager.mongodb.class%">
<argument type="service" id="doctrine_mongodb"/>
<argument type="service" id="annotation_reader"/>
</service>
</services>
</container>

View file

@ -1,27 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="fos_elastica.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\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true">
<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\ElasticaBundle\Doctrine\Listener" public="false">
<service id="fos_elastica.listener.prototype.orm" class="%fos_elastica.listener.prototype.orm.class%" public="false" abstract="true">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument/> <!-- identifier -->
<argument /> <!-- check method -->
<argument type="service" id="fos_elastica.indexable" />
<argument type="collection" /> <!-- configuration -->
<argument>null</argument> <!-- logger -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer" public="false">
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="%fos_elastica.elastica_to_model_transformer.prototype.orm.class%" public="false" abstract="true">
<argument type="service" id="doctrine" />
<argument /> <!-- model -->
<argument type="collection" /> <!-- options -->
@ -30,11 +41,9 @@
</call>
</service>
<service id="fos_elastica.manager.orm" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<argument type="service" id="doctrine"/>
<argument type="service" id="annotation_reader"/>
<service id="fos_elastica.manager.orm" class="%fos_elastica.manager.orm.class%">
<argument type="service" id="doctrine" />
<argument type="service" id="annotation_reader" />
</service>
</services>
</container>

View file

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

View file

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

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="fos_elastica.provider_registry.class">FOS\ElasticaBundle\Provider\ProviderRegistry</parameter>
</parameters>
<services>
<service id="fos_elastica.provider_registry" class="%fos_elastica.provider_registry.class%">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
</services>
</container>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="fos_elastica.serializer_callback_prototype" public="false" abstract="true">
<call method="setSerializer">
<argument type="service" id="fos_elastica.serializer" />
</call>
</service>
</services>
</container>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="fos_elastica.config_source.container" class="FOS\ElasticaBundle\Configuration\Source\ContainerSource" public="false">
<argument type="collection" /> <!-- index configs -->
<tag name="fos_elastica.config_source" />
</service>
</services>
</container>

View file

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

View file

@ -0,0 +1,45 @@
Aliased Indexes
===============
You can set up FOSElasticaBundle to use aliases for indexes which allows you to run an
index population without resetting the index currently being used by the application.
> *Note*: When you're using an alias, resetting an individual type will still cause a
> reset for that type.
To configure FOSElasticaBundle to use aliases for an index, set the use_alias option to
true.
```yaml
fos_elastica:
indexes:
website:
use_alias: true
```
The process for setting up aliases on an existing application is slightly more complicated
because the bundle is not able to set an alias as the same name as an index. You have some
options on how to handle this:
1) Delete the index from Elasticsearch. This option will make searching unavailable in your
application until a population has completed itself, and an alias is created.
2) Change the index_name parameter for your index to something new, and manually alias the
current index to the new index_name, which will then be replaced when you run a repopulate.
```yaml
fos_elastica:
indexes:
website:
use_alias: true
index_name: website_prod
```
```bash
$ curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions" : [
{ "add" : { "index" : "website", "alias" : "website_prod" } }
]
}'
```

View file

@ -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,42 +23,46 @@ 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;
use FOS\ElasticaBundle\Configuration\Search;
use FOS\ElasticaBundle\Annotation\Search;
/**
* @Search(repositoryClass="Acme\ElasticaBundle\SearchRepository\UserRepository")
@ -69,4 +73,4 @@ class User
//---
}
```
```

View file

@ -8,7 +8,7 @@ index and type for which the service will provide.
# app/config/config.yml
services:
acme.search_provider.user:
class: Acme\UserBundle\Search\UserProvider
class: Acme\UserBundle\Provider\UserProvider
arguments:
- @fos_elastica.index.website.user
tags:

View file

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

View file

@ -1,5 +1,5 @@
Suppressing Server Errors
========================
=========================
By default, exceptions from the Elastica client library will propagate through
the bundle's Client class. For instance, if the Elasticsearch server is offline,
@ -12,25 +12,48 @@ container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
```
Sample client code:
-------------------
```php
<?php
namespace Acme\ElasticaBundle;
use FOS\ElasticaBundle\Client as BaseClient;
use Elastica\Exception\ExceptionInterface;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Elastica\Client as BaseClient;
class Client extends BaseClient
{
public function request($path, $method, $data = array())
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
try {
return parent::request($path, $method, $data);
return parent::request($path, $method, $data, $query);
} catch (ExceptionInterface $e) {
if ($this->_logger) {
$this->_logger->warning('Failed to send a request to ElasticSearch', array(
'exception' => $e->getMessage(),
'path' => $path,
'method' => $method,
'data' => $data,
'query' => $query
));
}
return new Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
}
```
```
Configuration change:
---------------------
You must update a parameter in your `app/config/config.yml` file to point to your overridden client:
```yaml
parameters:
fos_elastica.client.class: Acme\ElasticaBundle\Client
```

View file

@ -12,7 +12,11 @@ Available documentation for FOSElasticaBundle
Cookbook Entries
----------------
* [Aliased Indexes](cookbook/aliased-indexes.md)
* [Custom Indexed Properties](cookbook/custom-properties.md)
* [Custom Repositories](cookbook/custom-repositories.md)
* [HTTP Headers for Elastica](cookbook/elastica-client-http-headers.md)
* Performance - [Logging](cookbook/logging.md)
* [Manual Providers](cookbook/manual-provider.md)
* [Clustering - Multiple Connections](cookbook/multiple-connections.md)
* [Suppressing server errors](cookbook/suppress-server-errors.md)

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
@ -48,27 +58,30 @@ fos_elastica:
clients:
default: { host: localhost, port: 9200 }
indexes:
search: ~
app: ~
```
In this example, an Elastica index (an instance of `Elastica\Index`) is available as a
service with the key `fos_elastica.index.search`.
service with the key `fos_elastica.index.app`.
If the Elasticsearch index name needs to be different to the service name in your
application, for example, renaming the search index based on different environments.
You may want the index `app` to be named something else on ElasticSearch depending on
if your application is running in a different env or other conditions that suit your
application. To set your customer index to a name that depends on the environment of your
Symfony application, use the example below:
```yaml
#app/config/config.yml
fos_elastica:
indexes:
search:
index_name: search_dev
app:
index_name: app_%kernel.environment%
```
In this case, the service `fos_elastica.index.search` will be using an Elasticsearch
index of search_dev.
In this case, the service `fos_elastica.index.app` will relate to an ElasticSearch index
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.
@ -81,7 +94,7 @@ will end up being indexed.
```yaml
fos_elastica:
indexes:
search:
app:
types:
user:
mappings:
@ -92,7 +105,7 @@ fos_elastica:
```
Each defined type is made available as a service, and in this case the service key is
`fos_elastica.index.search.user` and is an instance of `Elastica\Type`.
`fos_elastica.index.app.user` and is an instance of `Elastica\Type`.
FOSElasticaBundle requires a provider for each type that will notify when an object
that maps to a type has been modified. The bundle ships with support for Doctrine and
@ -114,15 +127,15 @@ Below is an example for the Doctrine ORM.
driver: orm
model: Acme\ApplicationBundle\Entity\User
provider: ~
listener: ~
listener:
immediate: ~
finder: ~
immediate: ~
```
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
@ -137,7 +150,7 @@ $ php app/console fos:elastica:populate
The command will also create all indexes and types defined if they do not already exist
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
-----------------------------------------------
@ -149,6 +177,52 @@ analyzer, you could write:
title: { boost: 8, analyzer: my_analyzer }
```
Testing if an object should be indexed
--------------------------------------
FOSElasticaBundle can be configured to automatically index changes made for
different kinds of objects if your persistence backend supports these methods,
but in some cases you might want to run an external service or call a property
on the object to see if it should be indexed.
A property, `indexable_callback` is provided under the type configuration that
lets you configure this behaviour which will apply for any automated watching
for changes and for a repopulation of an index.
In the example below, we're checking the enabled property on the user to only
index enabled users.
```yaml
types:
users:
indexable_callback: 'enabled'
```
The callback option supports multiple approaches:
* A method on the object itself provided as a string. `enabled` will call
`Object->enabled()`. Note that this does not support chaining methods with dot notation
like property paths. To achieve something similar use the ExpressionLanguage option
below.
* An array of a service id and a method which will be called with the object as the first
and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
method on a service defined as my_custom_service.
* An array of a class and a static method to call on that class which will be called with
the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
will call Acme\DemoBundle\IndexableChecker::isIndexable($object)
* A single element array with a service id can be used if the service has an __invoke
method. Such an invoke method must accept a single parameter for the object to be indexed.
`[ @my_custom_invokable_service ]`
* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
expression provided as a string. The object being indexed will be supplied as `object`
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more
information on the ExpressionLanguage component and its capabilities see its
[documentation](http://symfony.com/doc/current/components/expression_language/index.html)
In all cases, the callback should return a true or false, with true indicating it will be
indexed, and a false indicating the object should not be indexed, or should be removed
from the index if we are running an update.
Provider Configuration
----------------------
@ -188,6 +262,20 @@ persistence configuration.
identifier: searchId
```
### Turning on the persistence backend logger in production
FOSElasticaBundle will turn of your persistence backend's logging configuration by default
when Symfony2 is not in debug mode. You can force FOSElasticaBundle to always disable
logging by setting debug_logging to false, to leave logging alone by setting it to true,
or leave it set to its default value which will mirror %kernel.debug%.
```yaml
user:
persistence:
provider:
debug_logging: false
```
Listener Configuration
----------------------
@ -220,49 +308,6 @@ You can also choose to only listen for some of the events:
> **Propel** doesn't support this feature yet.
### Checking an entity method for listener
If you use listeners to update your index, you may need to validate your
entities before you index them (e.g. only index "public" entities). Typically,
you'll want the listener to be consistent with the provider's query criteria.
This may be achieved by using the `is_indexable_callback` config parameter:
```yaml
persistence:
listener:
is_indexable_callback: "isPublic"
```
If `is_indexable_callback` is a string and the entity has a method with the
specified name, the listener will only index entities for which the method
returns `true`. Additionally, you may provide a service and method name pair:
```yaml
persistence:
listener:
is_indexable_callback: [ "%custom_service_id%", "isIndexable" ]
```
In this case, the callback_class will be the `isIndexable()` method on the specified
service and the object being considered for indexing will be passed as the only
argument. This allows you to do more complex validation (e.g. ACL checks).
If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language)
component installed, you can use expressions to evaluate the callback:
```yaml
persistence:
listener:
is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')"
```
As you might expect, new entities will only be indexed if the callback_class returns
`true`. Additionally, modified entities will be updated or removed from the
index depending on whether the callback_class returns `true` or `false`, respectively.
The delete listener disregards the callback_class.
> **Propel** doesn't support this feature yet.
Flushing Method
---------------

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
@ -45,7 +47,7 @@ $companies = $finder->findPaginated($query);
$companies->setMaxPerPage($params['limit']);
$companies->setCurrentPage($params['page']);
$facets = $companies->getAdapter()->getFacets());
$facets = $companies->getAdapter()->getFacets();
```
Searching the entire index
@ -65,7 +67,7 @@ You can now use the index wide finder service `fos_elastica.finder.website`:
```php
/** var FOS\ElasticaBundle\Finder\MappedFinder */
$finder = $container->get('fos_elastica.finder.website');
$finder = $this->container->get('fos_elastica.finder.website');
// Returns a mixed array of any objects mapped
$results = $finder->find('bob');
@ -91,7 +93,7 @@ An example for using a repository:
```php
/** var FOS\ElasticaBundle\Manager\RepositoryManager */
$repositoryManager = $container->get('fos_elastica.manager');
$repositoryManager = $this->container->get('fos_elastica.manager');
/** var FOS\ElasticaBundle\Repository */
$repository = $repositoryManager->getRepository('UserBundle:User');
@ -160,7 +162,7 @@ fos_elastica:
site:
settings:
index:
analysis:
analysis:
analyzer:
my_analyzer:
type: snowball
@ -184,7 +186,7 @@ The following code will execute a search against the Elasticsearch server:
$finder = $this->container->get('fos_elastica.finder.site.article');
$boolQuery = new \Elastica\Query\Bool();
$fieldQuery = new \Elastica\Query\Text();
$fieldQuery = new \Elastica\Query\Match();
$fieldQuery->setFieldQuery('title', 'I am a title string');
$fieldQuery->setFieldParam('title', 'analyzer', 'my_analyzer');
$boolQuery->addShould($fieldQuery);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -4,6 +4,9 @@
{% set icon %}
<img alt="elastica" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAYAAABlL09dAAAABGdBTUEAALGPC/xhBQAAA/BpQ0NQSUNDIFByb2ZpbGUAACiRjVXdb9tUFD+Jb1ykFj+gsY4OFYuvVVNbuRsarcYGSZOl6UIauc3YKqTJdW4aU9c2ttNtVZ/2Am8M+AOAsgcekHhCGgzE9rLtAbRJU0EV1SSkPXTaQGiT9oKqcK6vU7tdxriRr38553c+79E1QMdXmuOYSRlg3vJdNZ+Rj5+YljtWIQnPQSf0QKeme066XC4CLsaFR9bDXyHB3jcH2uv/c3VWqacDJJ5CbFc9fR7xaYCUqTuuDyDeRvnwKd9B3PE84h0uJohYYXiW4yzDMxwfDzhT6ihilouk17Uq4iXE/TMx+WwM8xyCtSNPLeoausx6UXbtmmHSWLpPUP/PNW82WvF68eny5iaP4ruP1V53x9QQf65ruUnELyO+5vgZJn8V8b3GXCWNeC9A8pmae6TC+ck3FutT7yDeibhq+IWpUL5ozZQmuG1yec4+qoaca7o3ij2DFxHfqtNCkecjQJVmc6xfiHvrjbHQvzDuLUzmWn4W66Ml7kdw39PGy4h7EH/o2uoEz1lYpmZe5f6FK45fDnMQ1i2zVOQ+iUS9oMZA7tenxrgtOeDjIXJbMl0zjhRC/pJjBrOIuZHzbkOthJwbmpvLcz/kPrUqoc/UrqqWZb0dRHwYjiU0oGDDDO46WLABMqiQhwy+HXBRUwMDTJRQ1FKUGImnYQ5l7XnlgMNxxJgNrNeZNUZpz+ER7oQcm3QThezH5yApkkNkmIyATN4kb5HDJIvSEXJw07Yci89i3dn08z400CvjHYPMuZ5GXxTvrHvS0K9/9PcWa/uRnGkrn3gHwMMOtJgD8fqvLv2wK/KxQi68e7Pr6hJMPKm/qdup9dQK7quptYiR+j21hr9VSGNuZpDRPD5GkIcXyyBew2V8fNBw/wN5doy3JWLNOtcTaVgn6AelhyU42x9Jld+UP5UV5QvlvHJ3W5fbdkn4VPhW+FH4Tvhe+Blk4ZJwWfhJuCJ8I1yMndXj52Pz7IN6W9UyTbteUzCljLRbeknKSi9Ir0jFyJ/ULQ1JY9Ie1OzePLd4vHgtBpzAvdXV9rE4r4JaA04FFXhBhy04s23+Q2vSS4ZIYdvUDrNZbjHEnJgV0yCLe8URcUgcZ7iVn7gHdSO457ZMnf6YCmiMFa9zIJg6NqvMeiHQeUB9etpnF+2o7Zxxjdm6L+9TlNflNH6qqFyw9MF+WTNNOVB5sks96i7Q6iCw7yC/oh+owfctsfN6JPPfBjj0F95ZNyLZdAPgaw+g+7VI1od34rOfAVw4oDfchfDOTyR+AfBq+/fxf10ZvJtuNZsP8L7q+ARg4+Nm85/lZnPjS/S/BnDJ/BdZAHF4xCjCQAAAAAlwSFlzAAALEwAACxMBAJqcGAAABWZJREFUSIm1lU9sXFcVxr9773tv/o8n5sWeGQejTIwpoXYlUCQCUiBSRTdRpS5ihLKxiJBASBFVkViwiOpNxCYREixSdimoi0pATAQJEkJIDlWUtK5K4iKc1HatYtfO+N+8PzPvvnMOCzyWxy2BBT2bK7177u9+99N331Uigk+i9CdCBeD8L00TExOmr6/PN8Z83RjzLaXUF40xeQBzxpjfWGt/6/v+BxcvXky6a9R/s+LChQuZKIqe1Vr/SGv9FaVUopRKjTGitTZa64zW+j2l1BXP816/dOnShojIExVPTEyYUqn0nIhcBjDkuu7Dcrm8UC6XN7XWAuBQq9UaDsNwGMDLcRwXJycnfwag/URwqVT6DIApAJ/O5XKzjUbjzVqttt3f30+FQqFQ7is7YRAuz87OBvPz858noheLxeIbAG7vgZVS6sSJE4Ou646naZoS0ZtjY2PnAIx7nvePkZGRuyMjI9uFQgHZbFZXKpXC4ODgEc/z6gMDA+udTuf9hYWFUQAvAbi9l4qTJ09mtdaTzHwFwA8ymcxRpdQLRESVSuXdI0eObBeLReV5nvI8T4hoKwzDOcdxglqtdvj48eM7xpiAmZ8D9sWNiPIATouIEpE/VKtVF8CA1jqsVCrNUqkkruvC8zxorZVSKu10Oh9GUfQ2gG3f97czmUyLiHIHwUpEHBGZ1Vr/rlgspsYY7bqudV03dRwHWmtoreE4DjzPg+M4ipmb1tp3rbUrImKJCD3gbjFzGoahFZEPRMSKSCZN0ywRGWOM6kK76gFEYRgubG5u2iRJvI+AjTEiIujm+tq1a2tJkiwxcyGKouFsNlvNZDL5/aodxwEAabfbvLS0NBAEwSHP8zZ6wMxcIyKfiGg3o4jj+GqSJMna2trxxcXFwU6nUxCR/WtkZ2cHc3Nz/qNHj55SSmWLxeIvgH1XmpmnRMRn5ncAhLvg3wO4wczP37lz5wsbGxtJo9EIqtVqEIYhOp2ONz8/X7t///4zGxsbjVKp9FYul3vlILifiDqppK04iAkAbty4sXnmzJmXHccZbTabY3Ecl5eXl4/29/dvGWMkCILC2tracBzHh/L5/N9c150aHx9/vwdsrf2xiPwcgi87jjMNoA0Ap06dsq1WC4uLi6rVapWTJPlss9l0RUSladoGsGqM+SOAV9M0nTl79iz1gIno70qpx0TkEZEGgNOnTzvHjh37dqFQeMpxHCRJ8ra1dpqEjFHGYebtlZUVLwiCpa2trfcePnyYXL58GQetUAAUM+9Fr16vP9PpdL63tbXlRVG0w8yvlcvlq5wwcqWcevDgQd/CwsJPiei7InKrUqlMAVjqAQdBgFwuh/1gAD9JkqQUxzGCIHij3W7/6ubNm0F3slqtMhH9GcBRpdRXd8dlEeGeC9LNsO/76ty5c5NKqS/FcSxRFK1aa1+9e/fuh/v7V1dXw2azeVVEfikiqYiY7lwPmIhARHz+/PmjtVrtO9baviiK0G63b1trb8nHvwqamYmZsyIy1HXhoGIhIp3L5b4xOjr6ucP+YeW67rq19pV79+49/hgoAKQiMiciLWZ+qVAo9Pd4vKtYMTNmZmbeqdfrfzKOebbRaPzadd2Z/wD9NzlNZXcD0lrzR8Ddf8X169f/Wq/X79dqtemxsbG3pqenoydwDTM/LSKfAjAVRdEWsO8xHRoa8kXkdRF5rJT64crKyj8BqCcp3a2s53kvisg3ReT7aZr+RUR4T3Gr1ZJ8Pp+IyEkRueL7figiB+OH7jdrLUQEROQw89NEJCKSdvv2wMPDw6319fVbAL4G4PnuSbTWezHsWsXMXSiYGUTEAF4D8EhEuMeK/3f9C5VtKG2arhqTAAAAAElFTkSuQmCC" />
<span class="sf-toolbar-status">{{ collector.querycount }}</span>
{% if collector.querycount > 0 %}
<span class="sf-toolbar-info-piece-additional-detail">in {{ '%0.2f'|format(collector.time * 1000) }} ms</span>
{% endif %}
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
@ -20,10 +23,11 @@
{% block menu %}
<span class="label">
<span class="icon"><img src="{{ asset('bundles/foselastica/images/elastica.png') }}" alt="" /></span>
<span class="icon"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAcCAQAAADPJofWAAAAAXNSR0IArs4c6QAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2woEDg0xnxGaxwAAA51JREFUOMt100ts1FUUx/Hvufc/8x9mOrTTDpbysEVsqTzUqAsRgpqQGIJiIhtZSEJcqNGFJrSiZSPYkKALBUyAiLEuWKjBhdFAaEwwPNQqEAgEaA0FEWh4lE477/+9x0WLsST+Fmf1ycnJvecI/0knW4ENgZkhr5gVMtcEMmB64z1msCOCboR70tlkOuQdQREMBovF7tQt71/mXtzZQo8s89jLycFETiSqKd/v5wSY32VN18Ak3JGiR1d7an+Y3Z/Np7yVeCY/9czU4efF2iPxZ+wLXPgXP7WCTZ7MvvlnZlbqSJnGGbMeam5pk6HTIw9qs7tiH6D9vrbXW9e1Tus/sbhHZ8VOtfU1uZSExCUe1ExLtqayjcPnx4qNssjOs3TQrY9rYdVB2aFRw7HWq2kbJxCjFAsDoY+3hbkbw9ebNBsQ6EJVNlb3yQIDY8lcaAIsBisxiSXGToTNWqi/JUWXMIoabrK/95ymDNYHzmDUYAiwWOImOle+EI2AxyiKVydQGVAIoyneiBUNsAQEWIn+LA2NJqOYw3hUFQV2DFbu+FSuKTk9kRGMBoyPE7m8vza7UhMrmQNlV+NwAlDZXmXkkcHaeNamQBCEiLyeb7w631C71yxfr8/6YT8GUP6ifKlcf3xJX26kWNQyJfL+TvVs3S8rCw31hfRm2/KzL+iGQz8BnLjz8EVd5TPXF9wI/E0ZK/gr6SOLj7+Yz9RRu2bmMXm6y2/0A/rS0f6JL985+prVQAJiKEU8kLid6ezaA8Gh7iVP6BKtHad7njQr+xmSeFELLi2YovwlJ5Nbu07DNoLFoVNVrwBvh6feSMwSDcVuLvWF0y3Brd9mDlRlTsuVweGPCRzI+NOBW2rW3iYv5ZPfbrm7ji2fx1/Vkn7U/OGlivE4HA5Yh+yqUKFAYa3QfXcVf/R7Segy3wDGo4x3Tr3H3LLmqX6mZ5+jC4A5DO7jLR3SmAoYj8eTqx5uMR+UKErp78q2/e7ARNuLgE940bR1E9jpcKx+16MxJUd5+68XJp+a5DnqFxU/yYZG8XiSfJ+awkIW9WW+zLF8sh6NeqJb0cs+biIcKjW8u3T3xj+uZXZ/PbSe3km2nHWbogb3ZlSgPWz/bt71tsf432Qa64bSh5PTIFAU0pqdG3oRIiCgBBgsVTwRpYwmnWgMAlUd1aR+6m+DA4QqBlDKRDgifCqq8WO+CoFW9Ctd7dvBA+NV8DgiognuSv4bbsA/e9y1PQRZggAAAAAASUVORK5CYII=" alt="" /></span>
<strong>Elastica</strong>
<span class="count">
<span>{{ collector.querycount }}</span>
<span>{{ '%0.0f'|format(collector.time * 1000) }} ms</span>
</span>
</span>
{% endblock %}

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,18 +32,16 @@ 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".');
}
}
public function serialize($object)
{
$context = $this->serializer instanceof SerializerInterface ? new SerializationContext() : array();
$context = $this->serializer instanceof SerializerInterface ? SerializationContext::create()->enableMaxDepthChecks() : array();
if ($this->groups) {
if (!empty($this->groups)) {
$context->setGroups($this->groups);
}

View file

@ -32,13 +32,17 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
if (null != $facets) {
$event->setCustomPaginationParameter('facets', $facets);
}
$aggregations = $results->getAggregations();
if (null != $aggregations) {
$event->setCustomPaginationParameter('aggregations', $aggregations);
}
$event->stopPropagation();
}
}
/**
* Adds knp paging sort to query
* Adds knp paging sort to query.
*
* @param ItemsEvent $event
*/
@ -70,7 +74,7 @@ class PaginateElasticaQuerySubscriber implements EventSubscriberInterface
public static function getSubscribedEvents()
{
return array(
'knp_pager.items' => array('items', 1)
'knp_pager.items' => array('items', 1),
);
}
}
}

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