From e0ecda9f28a8f2f92e9dcb3142d923eb12c29aaa Mon Sep 17 00:00:00 2001 From: floriansemm Date: Tue, 7 Feb 2017 15:03:00 +0100 Subject: [PATCH 01/42] add return types --- Solr.php | 46 +++++++++++++++++++++++----------------- SolrInterface.php | 15 +++++++------ Tests/SolrClientFake.php | 12 ++++++----- Tests/SolrTest.php | 17 +++++++++------ 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/Solr.php b/Solr.php index f7f82aad..e4b1b043 100644 --- a/Solr.php +++ b/Solr.php @@ -1,18 +1,19 @@ solrClientCore; } @@ -84,7 +86,7 @@ public function getClient() /** * @return EntityMapper */ - public function getMapper() + public function getMapper(): EntityMapper { return $this->entityMapper; } @@ -92,7 +94,7 @@ public function getMapper() /** * @return MetaInformationFactory */ - public function getMetaFactory() + public function getMetaFactory(): MetaInformationFactory { return $this->metaInformationFactory; } @@ -102,7 +104,7 @@ public function getMetaFactory() * * @return SolrQuery */ - public function createQuery($entity) + public function createQuery($entity): SolrQuery { $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -121,7 +123,7 @@ public function createQuery($entity) * * @return QueryBuilderInterface */ - public function getQueryBuilder($entity) + public function getQueryBuilder($entity): QueryBuilderInterface { $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -131,7 +133,7 @@ public function getQueryBuilder($entity) /** * {@inheritdoc} */ - public function getRepository($entity) + public function getRepository($entity): RepositoryInterface { $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -152,7 +154,7 @@ public function getRepository($entity) /** * {@inheritdoc} */ - public function createQueryBuilder($entity) + public function createQueryBuilder($entity): QueryBuilderInterface { $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -193,7 +195,7 @@ public function removeDocument($entity) /** * {@inheritdoc} */ - public function addDocument($entity) + public function addDocument($entity): bool { $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -209,6 +211,8 @@ public function addDocument($entity) $this->addDocumentToIndex($doc, $metaInformation, $event); $this->eventManager->dispatch(Events::POST_INSERT, $event); + + return true; } /** @@ -219,7 +223,7 @@ public function addDocument($entity) * * @throws SolrException if callback method not exists */ - private function addToIndex(MetaInformationInterface $metaInformation, $entity) + private function addToIndex(MetaInformationInterface $metaInformation, $entity): bool { if (!$metaInformation->hasSynchronizationFilter()) { return true; @@ -238,9 +242,9 @@ private function addToIndex(MetaInformationInterface $metaInformation, $entity) * * @param AbstractQuery $query * - * @return \Solarium\QueryType\Select\Query\Query + * @return SolariumQuery */ - public function getSelectQuery(AbstractQuery $query) + public function getSelectQuery(AbstractQuery $query): SolariumQuery { $selectQuery = $this->solrClientCore->createSelect($query->getOptions()); @@ -255,7 +259,7 @@ public function getSelectQuery(AbstractQuery $query) /** * {@inheritdoc} */ - public function query(AbstractQuery $query) + public function query(AbstractQuery $query): array { $entity = $query->getEntity(); $runQueryInIndex = $query->getIndex(); @@ -287,13 +291,15 @@ public function query(AbstractQuery $query) * * @return integer */ - public function getNumFound() + public function getNumFound(): int { return $this->numberOfFoundDocuments; } /** * clears the whole index by using the query *:* + * + * @throws SolrException if an error occurs */ public function clearIndex() { @@ -351,7 +357,7 @@ public function synchronizeIndex($entities) /** * {@inheritdoc} */ - public function updateDocument($entity) + public function updateDocument($entity): bool { $metaInformations = $this->metaInformationFactory->loadInformation($entity); @@ -374,9 +380,9 @@ public function updateDocument($entity) /** * @param MetaInformationInterface $metaInformation * - * @return Document + * @return DocumentInterface */ - private function toDocument(MetaInformationInterface $metaInformation) + private function toDocument(MetaInformationInterface $metaInformation): DocumentInterface { $doc = $this->entityMapper->toDocument($metaInformation); @@ -388,7 +394,7 @@ private function toDocument(MetaInformationInterface $metaInformation) * @param MetaInformationInterface $metaInformation * @param Event $event * - * @throws SolrException + * @throws SolrException if an error occurs */ private function addDocumentToIndex($doc, MetaInformationInterface $metaInformation, Event $event) { diff --git a/SolrInterface.php b/SolrInterface.php index adf12feb..8ac1b3c4 100644 --- a/SolrInterface.php +++ b/SolrInterface.php @@ -5,6 +5,7 @@ use FS\SolrBundle\Query\AbstractQuery; use FS\SolrBundle\Query\QueryBuilderInterface; use FS\SolrBundle\Repository\Repository; +use FS\SolrBundle\Repository\RepositoryInterface; interface SolrInterface { @@ -19,7 +20,7 @@ public function removeDocument($entity); * * @return bool */ - public function addDocument($entity); + public function addDocument($entity): bool; /** * @param AbstractQuery $query @@ -28,28 +29,28 @@ public function addDocument($entity); * * @throws SolrException */ - public function query(AbstractQuery $query); + public function query(AbstractQuery $query): array; /** * @param object|string $entity entity, entity-alias or classname * * @return bool */ - public function updateDocument($entity); + public function updateDocument($entity): bool; /** * @param object|string $entity entity, entity-alias or classname * - * @return Repository + * @return RepositoryInterface * - * @throws \RuntimeException if repository of the given $entityAlias does not extend FS\SolrBundle\Repository\Repository + * @throws SolrException if repository of the given $entityAlias does not extend FS\SolrBundle\Repository\Repository */ - public function getRepository($entity); + public function getRepository($entity): RepositoryInterface; /** * @param object|string $entity entity, entity-alias or classname * * @return QueryBuilderInterface */ - public function createQueryBuilder($entity); + public function createQueryBuilder($entity): QueryBuilderInterface; } \ No newline at end of file diff --git a/Tests/SolrClientFake.php b/Tests/SolrClientFake.php index 2a6da86c..ed20801a 100644 --- a/Tests/SolrClientFake.php +++ b/Tests/SolrClientFake.php @@ -6,6 +6,7 @@ use FS\SolrBundle\Query\QueryBuilderInterface; use FS\SolrBundle\Query\SolrQuery; use FS\SolrBundle\Repository\Repository; +use FS\SolrBundle\Repository\RepositoryInterface; use FS\SolrBundle\SolrInterface; class SolrClientFake implements SolrInterface @@ -36,8 +37,9 @@ public function getMetaFactory() return $this->metaFactory; } - public function addDocument($doc) + public function addDocument($doc): bool { + return true; } public function deleteByQuery($query) @@ -54,7 +56,7 @@ public function isCommited() return $this->commit; } - public function query(AbstractQuery $query) + public function query(AbstractQuery $query): array { $this->query = $query; @@ -92,12 +94,12 @@ public function removeDocument($entity) // TODO: Implement removeDocument() method. } - public function updateDocument($entity) + public function updateDocument($entity): bool { // TODO: Implement updateDocument() method. } - public function getRepository($entity) + public function getRepository($entity): RepositoryInterface { // TODO: Implement getRepository() method. } @@ -107,7 +109,7 @@ public function computeChangeSet(array $doctrineChangeSet, $entity) // TODO: Implement computeChangeSet() method. } - public function createQueryBuilder($entity) + public function createQueryBuilder($entity): QueryBuilderInterface { // TODO: Implement createQueryBuilder() method. } diff --git a/Tests/SolrTest.php b/Tests/SolrTest.php index 1676a406..c6709232 100644 --- a/Tests/SolrTest.php +++ b/Tests/SolrTest.php @@ -3,7 +3,6 @@ namespace FS\SolrBundle\Tests; use FS\SolrBundle\Query\QueryBuilderInterface; -use FS\SolrBundle\SolrException; use FS\SolrBundle\Tests\Fixtures\EntityWithInvalidRepository; use FS\SolrBundle\Tests\Fixtures\InvalidTestEntityFiltered; use FS\SolrBundle\Tests\Fixtures\ValidTestEntityFiltered; @@ -11,15 +10,9 @@ use FS\SolrBundle\Tests\Fixtures\EntityCore1; use FS\SolrBundle\Tests\Doctrine\Mapper\SolrDocumentStub; use FS\SolrBundle\Query\FindByDocumentNameQuery; -use FS\SolrBundle\Event\EventManager; -use FS\SolrBundle\Tests\SolrClientFake; use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; use FS\SolrBundle\Tests\Fixtures\EntityWithRepository; -use FS\SolrBundle\Doctrine\Mapper\MetaInformation; -use FS\SolrBundle\Tests\Util\MetaTestInformationFactory; -use FS\SolrBundle\Solr; use FS\SolrBundle\Tests\Fixtures\ValidEntityRepository; -use FS\SolrBundle\Tests\Util\CommandFactoryStub; use FS\SolrBundle\Query\SolrQuery; use Solarium\Plugin\BufferedAdd\BufferedAdd; use Solarium\QueryType\Update\Query\Document\Document; @@ -234,6 +227,11 @@ public function indexDocumentsGroupedByCore() ->with('bufferedadd') ->will($this->returnValue($bufferPlugin)); + + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); + $this->solr->synchronizeIndex(array($entity)); } @@ -266,6 +264,11 @@ public function setCoreToNullIfNoIndexExists() ->with('bufferedadd') ->will($this->returnValue($bufferPlugin)); + + $this->mapper->expects($this->exactly(2)) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); + $this->solr->synchronizeIndex(array($entity1, $entity2)); } From e5a1517922a2a1e9dbc5e440fd0dcc88191bbd0c Mon Sep 17 00:00:00 2001 From: Patrik Olofsson Date: Mon, 27 Feb 2017 16:04:45 +0100 Subject: [PATCH 02/42] Add scheme and dsn option. If dsn option is set, then it will resolved to scheme, host, port and path --- Client/Solarium/SolariumClientBuilder.php | 37 ++++++++++++++++++++--- DependencyInjection/Configuration.php | 2 ++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Client/Solarium/SolariumClientBuilder.php b/Client/Solarium/SolariumClientBuilder.php index 8ac00568..f2e10f7a 100644 --- a/Client/Solarium/SolariumClientBuilder.php +++ b/Client/Solarium/SolariumClientBuilder.php @@ -28,7 +28,7 @@ class SolariumClientBuilder implements Builder private $eventDispatcher; /** - * @param array $settings + * @param array $settings * @param EventDispatcherInterface $eventDispatcher */ public function __construct(array $settings, EventDispatcherInterface $eventDispatcher) @@ -38,7 +38,7 @@ public function __construct(array $settings, EventDispatcherInterface $eventDisp } /** - * @param string $pluginName + * @param string $pluginName * @param AbstractPlugin $plugin */ public function addPlugin($pluginName, AbstractPlugin $plugin) @@ -53,11 +53,40 @@ public function addPlugin($pluginName, AbstractPlugin $plugin) */ public function build() { - $solariumClient = new Client(array('endpoint' => $this->settings), $this->eventDispatcher); + $settings = []; + foreach ($this->settings as $name => $options) { + if (isset($options['dsn'])) { + unset( + $options['scheme'], + $options['host'], + $options['port'], + $options['path'] + ); + + $parsedDsn = parse_url($options['dsn']); + unset($options['dsn']); + if ($parsedDsn) { + $options['scheme'] = isset($parsedDsn['scheme']) ? $parsedDsn['scheme'] : 'http'; + if (isset($parsedDsn['host'])) { + $options['host'] = $parsedDsn['host']; + } + if (isset($parsedDsn['user'])) { + $auth = $parsedDsn['user'] . (isset($parsedDsn['pass']) ? ':' . $parsedDsn['pass'] : ''); + $options['host'] = $auth . '@' . $options['host']; + } + $options['port'] = isset($parsedDsn['port']) ? $parsedDsn['port'] : 80; + $options['path'] = isset($parsedDsn['path']) ? $parsedDsn['path'] : ''; + } + } + + $settings[$name] = $options; + } + + $solariumClient = new Client(array('endpoint' => $settings), $this->eventDispatcher); foreach ($this->plugins as $pluginName => $plugin) { $solariumClient->registerPlugin($pluginName, $plugin); } return $solariumClient; } -} \ No newline at end of file +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 706cb1e0..4d9bcc26 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -20,6 +20,8 @@ public function getConfigTreeBuilder() ->useAttributeAsKey('name') ->prototype('array') ->children() + ->scalarNode('dsn')->end() + ->scalarNode('scheme')->end() ->scalarNode('host')->end() ->scalarNode('port')->end() ->scalarNode('path')->end() From 3a74626d2e581b97c156bbf7b22dae6a87177ec6 Mon Sep 17 00:00:00 2001 From: Patrik Olofsson Date: Tue, 28 Feb 2017 10:01:05 +0100 Subject: [PATCH 03/42] Add documentation for the DSN option --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index e0732258..74a2bac2 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Finally, configure the bundle: fs_solr: endpoints: core0: + schema: http host: host port: 8983 path: /solr/core0 @@ -59,6 +60,22 @@ fs_solr: timeout: 5 ``` +Default values will be used for any option left out. + +#### With DSN + +``` yaml +# app/config/config.yml +fs_solr: + endpoints: + core0: + dsn: http://host:8983/solr + core: core0 + timeout: 5 +``` + +Any values in `schema`, `host`, `port` and `path` option, will be ignored if you use the `dsn` option. + ### Step 4: Configure your entities To make an entity indexed, you must add some annotations to your entity. Basic configuration requires two annotations: From ae10ce7594e6261c69b637a9b36b685b9167c03e Mon Sep 17 00:00:00 2001 From: Patrik Olofsson Date: Tue, 28 Feb 2017 17:54:40 +0100 Subject: [PATCH 04/42] Add test cases for DSN configuration --- .../Solarium/SolariumClientBuilderTest.php | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 Tests/Client/Solarium/SolariumClientBuilderTest.php diff --git a/Tests/Client/Solarium/SolariumClientBuilderTest.php b/Tests/Client/Solarium/SolariumClientBuilderTest.php new file mode 100644 index 00000000..63d42800 --- /dev/null +++ b/Tests/Client/Solarium/SolariumClientBuilderTest.php @@ -0,0 +1,131 @@ +defaultEndpoints = [ + 'unittest' => [ + 'schema' => 'http', + 'host' => '127.0.0.1', + 'port' => 8983, + 'path' => '/solr', + 'timeout' => 5, + 'core' => null + ] + ]; + } + + public function testCreateClientWithoutDsn() + { + $actual = $this->createClientWithSettings($this->defaultEndpoints); + + $endpoint = $actual->getEndpoint('unittest'); + $this->assertEquals('http://127.0.0.1:8983/solr/', $endpoint->getBaseUri()); + } + + public function testCreateClientWithoutDsnWithCore() + { + $this->defaultEndpoints['unittest']['core'] = 'core0'; + + $actual = $this->createClientWithSettings($this->defaultEndpoints); + + $endpoint = $actual->getEndpoint('unittest'); + $this->assertEquals('http://127.0.0.1:8983/solr/core0/', $endpoint->getBaseUri()); + } + + /** + * @param string $dsn + * @param string $expectedBaseUri + * @param string $message + * @dataProvider dsnProvider + */ + public function testCreateClientDsn($dsn, $expectedBaseUri, $message) + { + $settings = $this->defaultEndpoints; + $settings['unittest'] = [ + 'dsn' => $dsn + ]; + + $actual = $this->createClientWithSettings($settings); + + $endpoint = $actual->getEndpoint('unittest'); + $this->assertEquals($expectedBaseUri, $endpoint->getBaseUri(), $message); + } + + /** + * @param string $dsn + * @param string $expectedBaseUri + * @param string $message + * @dataProvider dsnProvider + */ + public function testCreateClientDsnWithCore($dsn, $expectedBaseUri, $message) + { + $settings = $this->defaultEndpoints; + $settings['unittest'] = [ + 'dsn' => $dsn, + 'core' => 'core0' + ]; + + $actual = $this->createClientWithSettings($settings); + + $endpoint = $actual->getEndpoint('unittest'); + $this->assertEquals($expectedBaseUri . 'core0/', $endpoint->getBaseUri(), $message . ' with core'); + } + + public function dsnProvider() + { + return [ + [ + 'http://example.com:1234', + 'http://example.com:1234/', + 'Test DSN without path and any authentication' + ], + [ + 'http://example.com:1234/solr', + 'http://example.com:1234/solr/', + 'Test DSN without any authentication' + ], + [ + 'http://user@example.com:1234/solr', + 'http://user@example.com:1234/solr/', + 'Test DSN with user-only authentication' + ], + [ + 'http://user:secret@example.com:1234/solr', + 'http://user:secret@example.com:1234/solr/', + 'Test DSN with authentication' + ], + [ + 'https://example.com:1234/solr', + 'https://example.com:1234/solr/', + 'Test DSN with HTTPS' + ] + ]; + } + + /** + * @param array $settings + * @return \Solarium\Client + */ + private function createClientWithSettings(array $settings) + { + /** @var EventDispatcherInterface $eventDispatcherMock */ + $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); + return (new SolariumClientBuilder($settings, $eventDispatcherMock))->build(); + } +} From 03040758fc020555a246aebc0d7689cb4c176bbf Mon Sep 17 00:00:00 2001 From: Patrik Olofsson Date: Tue, 28 Feb 2017 17:56:24 +0100 Subject: [PATCH 05/42] Rename test cases --- Tests/Client/Solarium/SolariumClientBuilderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Client/Solarium/SolariumClientBuilderTest.php b/Tests/Client/Solarium/SolariumClientBuilderTest.php index 63d42800..df1ad5e5 100644 --- a/Tests/Client/Solarium/SolariumClientBuilderTest.php +++ b/Tests/Client/Solarium/SolariumClientBuilderTest.php @@ -54,7 +54,7 @@ public function testCreateClientWithoutDsnWithCore() * @param string $message * @dataProvider dsnProvider */ - public function testCreateClientDsn($dsn, $expectedBaseUri, $message) + public function testCreateClientWithDsn($dsn, $expectedBaseUri, $message) { $settings = $this->defaultEndpoints; $settings['unittest'] = [ @@ -73,7 +73,7 @@ public function testCreateClientDsn($dsn, $expectedBaseUri, $message) * @param string $message * @dataProvider dsnProvider */ - public function testCreateClientDsnWithCore($dsn, $expectedBaseUri, $message) + public function testCreateClientWithDsnAndCore($dsn, $expectedBaseUri, $message) { $settings = $this->defaultEndpoints; $settings['unittest'] = [ From a32c265e602eb795b3f7928d307a0d298e3d0163 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 1 Mar 2017 09:41:33 +0100 Subject: [PATCH 06/42] code-cleanup --- Client/Solarium/SolariumClientBuilder.php | 4 ++-- .../Solarium/SolariumClientBuilderTest.php | 16 +++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Client/Solarium/SolariumClientBuilder.php b/Client/Solarium/SolariumClientBuilder.php index f2e10f7a..8c2d8ee8 100644 --- a/Client/Solarium/SolariumClientBuilder.php +++ b/Client/Solarium/SolariumClientBuilder.php @@ -28,7 +28,7 @@ class SolariumClientBuilder implements Builder private $eventDispatcher; /** - * @param array $settings + * @param array $settings * @param EventDispatcherInterface $eventDispatcher */ public function __construct(array $settings, EventDispatcherInterface $eventDispatcher) @@ -38,7 +38,7 @@ public function __construct(array $settings, EventDispatcherInterface $eventDisp } /** - * @param string $pluginName + * @param string $pluginName * @param AbstractPlugin $plugin */ public function addPlugin($pluginName, AbstractPlugin $plugin) diff --git a/Tests/Client/Solarium/SolariumClientBuilderTest.php b/Tests/Client/Solarium/SolariumClientBuilderTest.php index df1ad5e5..8a9723c0 100644 --- a/Tests/Client/Solarium/SolariumClientBuilderTest.php +++ b/Tests/Client/Solarium/SolariumClientBuilderTest.php @@ -5,17 +5,16 @@ use FS\SolrBundle\Client\Solarium\SolariumClientBuilder; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -/** - * Class SolariumClientBuilderTest - * @package FS\SolrBundle\Tests\Client\Solarium - */ class SolariumClientBuilderTest extends \PHPUnit_Framework_TestCase { /** - * @var + * @var array */ private $defaultEndpoints; + /** + * {@inheritdoc} + */ protected function setUp() { $this->defaultEndpoints = [ @@ -52,6 +51,7 @@ public function testCreateClientWithoutDsnWithCore() * @param string $dsn * @param string $expectedBaseUri * @param string $message + * * @dataProvider dsnProvider */ public function testCreateClientWithDsn($dsn, $expectedBaseUri, $message) @@ -71,6 +71,7 @@ public function testCreateClientWithDsn($dsn, $expectedBaseUri, $message) * @param string $dsn * @param string $expectedBaseUri * @param string $message + * * @dataProvider dsnProvider */ public function testCreateClientWithDsnAndCore($dsn, $expectedBaseUri, $message) @@ -87,6 +88,9 @@ public function testCreateClientWithDsnAndCore($dsn, $expectedBaseUri, $message) $this->assertEquals($expectedBaseUri . 'core0/', $endpoint->getBaseUri(), $message . ' with core'); } + /** + * @return array + */ public function dsnProvider() { return [ @@ -120,12 +124,14 @@ public function dsnProvider() /** * @param array $settings + * * @return \Solarium\Client */ private function createClientWithSettings(array $settings) { /** @var EventDispatcherInterface $eventDispatcherMock */ $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); + return (new SolariumClientBuilder($settings, $eventDispatcherMock))->build(); } } From 0e58f8be8eca651eaa86b253c7ebddfc1c1522dd Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 1 Mar 2017 09:54:13 +0100 Subject: [PATCH 07/42] format treebuilder configuration --- DependencyInjection/Configuration.php | 38 ++++++++++----------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 4d9bcc26..bcfa8496 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -17,32 +17,22 @@ public function getConfigTreeBuilder() $rootNode = $treeBuilder->root('fs_solr'); $rootNode->children() ->arrayNode('endpoints') - ->useAttributeAsKey('name') - ->prototype('array') - ->children() - ->scalarNode('dsn')->end() - ->scalarNode('scheme')->end() - ->scalarNode('host')->end() - ->scalarNode('port')->end() - ->scalarNode('path')->end() - ->scalarNode('core')->end() - ->scalarNode('timeout')->end() - ->booleanNode('active')->defaultValue(true)->end() + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('dsn')->end() + ->scalarNode('scheme')->end() + ->scalarNode('host')->end() + ->scalarNode('port')->end() + ->scalarNode('path')->end() + ->scalarNode('core')->end() + ->scalarNode('timeout')->end() + ->booleanNode('active')->defaultValue(true)->end() + ->end() + ->end() ->end() - ->end() - ->end() -// ->arrayNode('clients') -// ->useAttributeAsKey('name') -// ->prototype('array') -// ->children() -// ->arrayNode('endpoints') -// ->prototype('scalar')->end() -// ->end() -// ->end() -// ->end() -// ->end() ->booleanNode('auto_index')->defaultValue(true)->end() - ->end(); + ->end(); return $treeBuilder; } From 49d1990ff48ed4d2aee9bb4726858d9e4534c466 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 1 Mar 2017 09:56:22 +0100 Subject: [PATCH 08/42] add some phpdoc --- DataCollector/RequestCollector.php | 4 +--- DependencyInjection/Configuration.php | 2 +- DependencyInjection/FSSolrExtension.php | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/DataCollector/RequestCollector.php b/DataCollector/RequestCollector.php index 0b468fb4..cc096008 100644 --- a/DataCollector/RequestCollector.php +++ b/DataCollector/RequestCollector.php @@ -25,9 +25,7 @@ public function __construct(DebugLogger $logger) } /** - * @param Request $request - * @param Response $response - * @param \Exception|null $exception + * {@inheritdoc} */ public function collect(Request $request, Response $response, \Exception $exception = null) { diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index bcfa8496..955b051a 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -9,7 +9,7 @@ class Configuration implements ConfigurationInterface { /** - * @return TreeBuilder + * {@inheritdoc} */ public function getConfigTreeBuilder() { diff --git a/DependencyInjection/FSSolrExtension.php b/DependencyInjection/FSSolrExtension.php index b38e476e..601a613f 100644 --- a/DependencyInjection/FSSolrExtension.php +++ b/DependencyInjection/FSSolrExtension.php @@ -14,8 +14,7 @@ class FSSolrExtension extends Extension { /** - * @param array $configs - * @param ContainerBuilder $container + * {@inheritdoc} */ public function load(array $configs, ContainerBuilder $container) { From 9f4b09afadbb43bd5a6fbc24ef58ceed45ee3b29 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 6 Mar 2017 09:26:39 +0100 Subject: [PATCH 09/42] add class-documentation --- Doctrine/Mapper/MetaInformationInterface.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Doctrine/Mapper/MetaInformationInterface.php b/Doctrine/Mapper/MetaInformationInterface.php index 3fd26c7d..56d274cb 100644 --- a/Doctrine/Mapper/MetaInformationInterface.php +++ b/Doctrine/Mapper/MetaInformationInterface.php @@ -44,11 +44,15 @@ public function getIdentifier(); public function generateDocumentId(); /** + * Entity classname + * * @return string */ public function getClassName(); /** + * If the entity classname is Product, then the document will be product. + * * @return string */ public function getDocumentName(); @@ -59,11 +63,15 @@ public function getDocumentName(); public function getFields(); /** + * Classname of the document-repository + * * @return string */ public function getRepository(); /** + * Source/target entity instance + * * @return object */ public function getEntity(); @@ -72,6 +80,8 @@ public function getEntity(); * @param string $fieldName * * @return Field|null + * + * @throws \InvalidArgumentException if given $fieldName is unknown */ public function getField($fieldName); @@ -81,6 +91,8 @@ public function getField($fieldName); public function getFieldMapping(); /** + * The document boost value + * * @return number */ public function getBoost(); @@ -96,6 +108,8 @@ public function getSynchronizationCallback(); public function hasSynchronizationFilter(); /** + * Returns the configured index argument in FS\SolrBundle\Doctrine\Annotation\Document or the returns value of the index-handler callback + * * @return string */ public function getIndex(); @@ -108,11 +122,16 @@ public function getIndex(); public function getDocumentKey(); /** + * The property which has the FS\SolrBundle\Doctrine\Annotation\Id annotation + * * @return string */ public function getIdentifierFieldName(); /** + * Returns MetaInformationInterface::DOCTRINE_MAPPER_TYPE_DOCUMENT if target is an doctrine-odm object or + * MetaInformationInterface::DOCTRINE_MAPPER_TYPE_RELATIONAL if it is an doctrine-orm object, otherwise an empty string + * * @return string */ public function getDoctrineMapperType(); From efa26586659c42250691a43f31e82ec736dc3065 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 6 Mar 2017 09:30:30 +0100 Subject: [PATCH 10/42] add a document-helper class --- Helper/DocumentHelper.php | 73 +++++++++++++++++++++++++++++++++++++++ README.md | 9 +++++ Solr.php | 9 +++++ 3 files changed, 91 insertions(+) create mode 100644 Helper/DocumentHelper.php diff --git a/Helper/DocumentHelper.php b/Helper/DocumentHelper.php new file mode 100644 index 00000000..4d776686 --- /dev/null +++ b/Helper/DocumentHelper.php @@ -0,0 +1,73 @@ +solariumClient = $solr->getClient(); + $this->metaInformationFactory = $solr->getMetaFactory(); + } + + /** + * @param mixed $entity + * + * @return int + */ + public function getLastInsertDocumentId($entity) + { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + /** @var Query $select */ + $select = $this->solariumClient->createQuery(SolariumClient::QUERY_SELECT); + $select->setQuery(sprintf('id:%s*', $metaInformation->getDocumentKey())); + $select->setRows($this->getNumberOfDocuments($metaInformation->getDocumentName())); + $select->addFields(array('id')); + + $result = $this->solariumClient->select($select); + + if ($result->count() == 0) { + return 0; + } + + $ids = array_map(function ($document) { + return substr($document->id, stripos($document->id, '_') + 1); + }, $result->getIterator()->getArrayCopy()); + + return intval(max($ids)); + } + + /** + * @param string $documentKey + * + * @return int + */ + private function getNumberOfDocuments($documentKey) + { + $select = $this->solariumClient->createQuery(SolariumClient::QUERY_SELECT); + $select->setQuery(sprintf('id:%s_*', $documentKey)); + + $result = $this->solariumClient->select($select); + + return $result->getNumFound(); + } +} \ No newline at end of file diff --git a/README.md b/README.md index eeec2757..e0785ed5 100644 --- a/README.md +++ b/README.md @@ -503,3 +503,12 @@ To hook into the [Solarium events](http://solarium.readthedocs.io/en/stable/cust ```xml ``` + +## Document helper + +### Retrieve the last insert entity-id + +```php +$helper = $this->get('solr.client')->getDocumentHelper(); +$id = $helper->getLastInsertDocumentId(); +``` \ No newline at end of file diff --git a/Solr.php b/Solr.php index 51b21a2f..2ac82724 100644 --- a/Solr.php +++ b/Solr.php @@ -7,6 +7,7 @@ use FS\SolrBundle\Doctrine\Mapper\Mapping\MapAllFieldsCommand; use FS\SolrBundle\Doctrine\Mapper\Mapping\MapIdentifierCommand; use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; +use FS\SolrBundle\Helper\DocumentHelper; use FS\SolrBundle\Query\QueryBuilder; use FS\SolrBundle\Query\QueryBuilderInterface; use Solarium\Plugin\BufferedAdd\BufferedAdd; @@ -97,6 +98,14 @@ public function getMetaFactory() return $this->metaInformationFactory; } + /** + * @return DocumentHelper + */ + public function getDocumentHelper() + { + return new DocumentHelper($this); + } + /** * @param object|string $entity entity, entity-alias or classname * From 5b726d45646030d80816b04c9b3cc195ffc08005 Mon Sep 17 00:00:00 2001 From: Patrik Olofsson Date: Mon, 6 Mar 2017 14:42:02 +0100 Subject: [PATCH 11/42] Don't override the solr.auto_index parameter, if it already has been set, for example, by setting the env-variable SYMFONY__SOLR__AUTO_INDEX to 0, which can be necessary when you want to run a console command which persists many entities. --- DependencyInjection/FSSolrExtension.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DependencyInjection/FSSolrExtension.php b/DependencyInjection/FSSolrExtension.php index b38e476e..0ab3320c 100644 --- a/DependencyInjection/FSSolrExtension.php +++ b/DependencyInjection/FSSolrExtension.php @@ -29,7 +29,9 @@ public function load(array $configs, ContainerBuilder $container) $this->setupClients($config, $container); - $container->setParameter('solr.auto_index', $config['auto_index']); + if (!$container->hasParameter('solr.auto_index')) { + $container->setParameter('solr.auto_index', $config['auto_index']); + } $this->setupDoctrineListener($config, $container); $this->setupDoctrineConfiguration($config, $container); From 321232ac4e6cec6353339adc89b0cc149922cba7 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 22 Mar 2017 07:56:35 +0100 Subject: [PATCH 12/42] cache entity if it is was instanced once --- Doctrine/Mapper/MetaInformationFactory.php | 21 ++++++++++++++----- .../Mapper/MetaInformationFactoryTest.php | 14 +++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Doctrine/Mapper/MetaInformationFactory.php b/Doctrine/Mapper/MetaInformationFactory.php index dccb85ab..be3329bc 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -19,6 +19,11 @@ class MetaInformationFactory */ private $classnameResolver = null; + /** + * @var array + */ + private $objectCache; + /** * @param AnnotationReader $reader */ @@ -46,12 +51,18 @@ public function loadInformation($entity) { $className = $this->getClass($entity); - if (!is_object($entity)) { - $reflectionClass = new \ReflectionClass($className); - if (!$reflectionClass->isInstantiable()) { - throw new \RuntimeException(sprintf('cannot instantiate entity %s', $className)); + if (isset($this->objectCache[$className])) { + $entity = $this->objectCache[$className]; + } else { + if (!is_object($entity)) { + $reflectionClass = new \ReflectionClass($className); + if (!$reflectionClass->isInstantiable()) { + throw new \RuntimeException(sprintf('cannot instantiate entity %s', $className)); + } + $entity = $reflectionClass->newInstanceWithoutConstructor(); } - $entity = $reflectionClass->newInstanceWithoutConstructor(); + + $this->objectCache[$className] = $entity; } if (!$this->annotationReader->hasDocumentDeclaration($entity)) { diff --git a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php index b60a13c6..475be502 100644 --- a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php +++ b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php @@ -48,8 +48,6 @@ private function getClassnameResolverCouldNotResolveClassname() return $doctrineConfiguration; } - - public function testLoadInformation_ShouldLoadAll() { $testEntity = new ValidTestEntity(); @@ -169,5 +167,17 @@ public function determineDoctrineMapperTypeFromDocument() $this->assertEquals(MetaInformationInterface::DOCTRINE_MAPPER_TYPE_DOCUMENT, $metainformation->getDoctrineMapperType()); } + + /** + * @test + */ + public function useCachedEntityInstanceIfItIsSet() + { + $factory = new MetaInformationFactory($this->reader); + $metainformation1 = $factory->loadInformation(new ValidTestEntity()); + $metainformation2 = $factory->loadInformation(new ValidTestEntity()); + + $this->assertEquals($metainformation1->getEntity(), $metainformation2->getEntity()); + } } From 30bd142616741e2103fb943f4e58f3d91f17e2ff Mon Sep 17 00:00:00 2001 From: floriansemm Date: Fri, 24 Mar 2017 16:11:58 +0100 Subject: [PATCH 13/42] remove caching --- Doctrine/Mapper/MetaInformationFactory.php | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/Doctrine/Mapper/MetaInformationFactory.php b/Doctrine/Mapper/MetaInformationFactory.php index be3329bc..ebb16e41 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -19,11 +19,6 @@ class MetaInformationFactory */ private $classnameResolver = null; - /** - * @var array - */ - private $objectCache; - /** * @param AnnotationReader $reader */ @@ -51,18 +46,12 @@ public function loadInformation($entity) { $className = $this->getClass($entity); - if (isset($this->objectCache[$className])) { - $entity = $this->objectCache[$className]; - } else { - if (!is_object($entity)) { - $reflectionClass = new \ReflectionClass($className); - if (!$reflectionClass->isInstantiable()) { - throw new \RuntimeException(sprintf('cannot instantiate entity %s', $className)); - } - $entity = $reflectionClass->newInstanceWithoutConstructor(); + if (!is_object($entity)) { + $reflectionClass = new \ReflectionClass($className); + if (!$reflectionClass->isInstantiable()) { + throw new \RuntimeException(sprintf('cannot instantiate entity %s', $className)); } - - $this->objectCache[$className] = $entity; + $entity = $reflectionClass->newInstanceWithoutConstructor(); } if (!$this->annotationReader->hasDocumentDeclaration($entity)) { @@ -82,6 +71,7 @@ public function loadInformation($entity) $metaInformation->setIndex($this->annotationReader->getDocumentIndex($entity)); $metaInformation->setIsDoctrineEntity($this->isDoctrineEntity($entity)); $metaInformation->setDoctrineMapperType($this->getDoctrineMapperType($entity)); + $metaInformation->setNested($this->annotationReader->isNested($entity)); return $metaInformation; } From 7f348d17155e62d9ecd8275d2189c7e542b7edf6 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Fri, 24 Mar 2017 16:16:00 +0100 Subject: [PATCH 14/42] fix tests --- Doctrine/Mapper/MetaInformationFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Doctrine/Mapper/MetaInformationFactory.php b/Doctrine/Mapper/MetaInformationFactory.php index ebb16e41..dccb85ab 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -71,7 +71,6 @@ public function loadInformation($entity) $metaInformation->setIndex($this->annotationReader->getDocumentIndex($entity)); $metaInformation->setIsDoctrineEntity($this->isDoctrineEntity($entity)); $metaInformation->setDoctrineMapperType($this->getDoctrineMapperType($entity)); - $metaInformation->setNested($this->annotationReader->isNested($entity)); return $metaInformation; } From 5abf461ebb79a1a98d9996b4378e577552f0bf1d Mon Sep 17 00:00:00 2001 From: floriansemm Date: Fri, 21 Apr 2017 10:54:33 +0200 Subject: [PATCH 15/42] add hint how to index documents without database --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e0785ed5..4a2145a7 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,10 @@ Any values in `schema`, `host`, `port` and `path` option, will be ignored if you ### Step 4: Configure your entities To make an entity indexed, you must add some annotations to your entity. Basic configuration requires two annotations: -`@Solr\Document()`, `@Solr\Id()`. To index data add `@Solr\Field()` to your properties. +`@Solr\Document()`, `@Solr\Id()`. To index data add `@Solr\Field()` to your properties. + +If you want to index documents without any database, then you have to use the same annotations. Make sure you have set a Id or +set `@Solr\Id(generateId=true)`. ```php // .... From 9426bfe4138e1ab0ebd5a6b2bfb9608a2238482d Mon Sep 17 00:00:00 2001 From: floriansemm Date: Tue, 20 Jun 2017 22:32:55 +0200 Subject: [PATCH 16/42] fixes #165 --- Repository/Repository.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Repository/Repository.php b/Repository/Repository.php index 1b056a73..35d810ce 100644 --- a/Repository/Repository.php +++ b/Repository/Repository.php @@ -47,9 +47,11 @@ public function __construct(SolrInterface $solr, MetaInformationInterface $metaI */ public function find($id) { + $documentKey = $this->metaInformation->getDocumentName() . '_' . $id; + $query = new FindByIdentifierQuery(); $query->setIndex($this->metaInformation->getIndex()); - $query->setDocumentKey($this->metaInformation->getDocumentKey()); + $query->setDocumentKey($documentKey); $query->setEntity($this->metaInformation->getEntity()); $query->setSolr($this->solr); $query->setHydrationMode($this->hydrationMode); From 614660c693ae092404d4ed90729ba5038a593fdd Mon Sep 17 00:00:00 2001 From: Romain Pierre Date: Thu, 6 Jul 2017 00:18:15 +0200 Subject: [PATCH 17/42] Improves the debugger icon and view --- DataCollector/RequestCollector.php | 19 +++-- DependencyInjection/FSSolrExtension.php | 2 - Resources/views/Profiler/icon.svg | 8 +-- Resources/views/Profiler/solr.html.twig | 93 ++++++++++++++++--------- 4 files changed, 77 insertions(+), 45 deletions(-) diff --git a/DataCollector/RequestCollector.php b/DataCollector/RequestCollector.php index cc096008..034258d5 100644 --- a/DataCollector/RequestCollector.php +++ b/DataCollector/RequestCollector.php @@ -3,11 +3,11 @@ namespace FS\SolrBundle\DataCollector; use FS\SolrBundle\Logging\DebugLogger; -use FS\SolrBundle\Logging\SolrLoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; -use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\VarDumper\Cloner\VarCloner; class RequestCollector extends DataCollector { @@ -74,10 +74,17 @@ public function parseQuery($request) { list($endpoint, $params) = explode('?', $request['request']); - return array_merge($request, [ - 'endpoint' => $endpoint, - 'params' => $params - ]); + $request['endpoint'] = $endpoint; + $request['params'] = $params; + + if (class_exists(VarCloner::class)) { + $varCloner = new VarCloner(); + + parse_str($params, $stub); + $request['stub'] = Kernel::VERSION_ID >= 30200 ? $varCloner->cloneVar($stub) : $stub; + } + + return $request; } /** diff --git a/DependencyInjection/FSSolrExtension.php b/DependencyInjection/FSSolrExtension.php index b3c2a4b5..1a3f2745 100644 --- a/DependencyInjection/FSSolrExtension.php +++ b/DependencyInjection/FSSolrExtension.php @@ -2,8 +2,6 @@ namespace FS\SolrBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\DefinitionDecorator; -use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; diff --git a/Resources/views/Profiler/icon.svg b/Resources/views/Profiler/icon.svg index 3567873c..8b9214fa 100644 --- a/Resources/views/Profiler/icon.svg +++ b/Resources/views/Profiler/icon.svg @@ -1,7 +1,3 @@ - - - - - - + + \ No newline at end of file diff --git a/Resources/views/Profiler/solr.html.twig b/Resources/views/Profiler/solr.html.twig index ce17dbfd..a98c899a 100644 --- a/Resources/views/Profiler/solr.html.twig +++ b/Resources/views/Profiler/solr.html.twig @@ -3,45 +3,53 @@ {% block toolbar %} {% set profiler_markup_version = profiler_markup_version|default(1) %} - {% set icon %} + {% if collector.querycount > 0 %} {% if profiler_markup_version == 1 %} - - Solr - {{ collector.querycount }} - {% if collector.querycount > 0 %} - in {{ '%0.2f'|format(collector.time * 1000) }} ms - {% endif %} - + {% set icon %} + Solr + {{ collector.querycount }} + {% if collector.querycount > 0 %} + in {{ '%0.2f'|format(collector.time * 1000) }} ms + {% endif %} + {% endset %} + + {% set text %} +
+ Solr queries + {{ collector.querycount }} +
+
+ Query time + {{ '%0.2f'|format(collector.time * 1000) }} ms +
+ {% endset %} {% else %} + {% set status = collector.querycount > 50 ? 'yellow' %} - {% if collector.querycount > 0 %} - {% set status = collector.querycount > 50 ? 'yellow' %} - + {% set icon %} {{ include('@FSSolr/Profiler/icon.svg') }} - {{ collector.querycount }} in {{ '%0.2f'|format(collector.time * 1000) }} ms - {% endif %} - + {% endset %} + + {% set text %} +
+ Solr queries + {{ collector.querycount }} +
+
+ Query time + {{ '%0.2f'|format(collector.time * 1000) }} ms +
+ {% endset %} {% endif %} - {% endset %} - {% set text %} -
- Solr Queries - {{ collector.querycount }} -
-
- Query time - {{ '%0.2f'|format(collector.time * 1000) }} ms -
- {% endset %} - - {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} + {% endif %} {% endblock %} {% block menu %} @@ -50,7 +58,7 @@ {% if profiler_markup_version == 1 %} - + Solr Solr {{ collector.querycount }} @@ -76,6 +84,7 @@ {% block queries %} {% if profiler_markup_version == 1 %} + - {% endif %} -

Queries

+

Solr Queries

+ + {% else %} + +

Query Metrics

+
+
+ {{ collector.querycount }} + Solr queries +
+ +
+ {{ '%0.2f'|format(collector.time * 1000) }} ms + Query time +
+
+ +

Queries

+ + {% endif %} {% if collector.queries is empty %}
@@ -118,7 +145,11 @@ {{ query.endpoint }} - {{ query.params | replace({'&': "
"}) | raw }} + {% if query.stub is not defined %} + {{ query.params | replace({'&': "
"}) | raw }} + {% else %} + {{ profiler_dump(query.stub, 1) }} + {% endif %} {% endfor %} From e9e36e1e5f9eafb3af3dc78e68c345cbf668922d Mon Sep 17 00:00:00 2001 From: floriansemm Date: Thu, 6 Jul 2017 11:52:45 +0200 Subject: [PATCH 18/42] add request-method + request body --- Client/Solarium/Plugin/LoggerPlugin.php | 11 +++++-- DataCollector/RequestCollector.php | 4 ++- Logging/DebugLogger.php | 2 +- Logging/LoggerChain.php | 39 ------------------------- Logging/SolrLoggerInterface.php | 8 ++--- Resources/views/Profiler/solr.html.twig | 8 +++++ 6 files changed, 23 insertions(+), 49 deletions(-) delete mode 100644 Logging/LoggerChain.php diff --git a/Client/Solarium/Plugin/LoggerPlugin.php b/Client/Solarium/Plugin/LoggerPlugin.php index ddf02074..27c9ff0d 100644 --- a/Client/Solarium/Plugin/LoggerPlugin.php +++ b/Client/Solarium/Plugin/LoggerPlugin.php @@ -42,11 +42,18 @@ protected function initPluginType() public function preExecuteRequest(PreExecuteRequest $event) { $endpoint = $event->getEndpoint(); - $uri = $event->getRequest()->getUri(); + $request = $event->getRequest(); + $uri = $request->getUri(); $path = sprintf('%s://%s:%s%s/%s', $endpoint->getScheme(), $endpoint->getHost(), $endpoint->getPort(), $endpoint->getPath(), urldecode($uri)); - $this->logger->startRequest($path); + $requestInformation = [ + 'uri' => $path, + 'method' => $request->getMethod(), + 'raw_data' => $request->getRawData() + ]; + + $this->logger->startRequest($requestInformation); } /** diff --git a/DataCollector/RequestCollector.php b/DataCollector/RequestCollector.php index 034258d5..d5189044 100644 --- a/DataCollector/RequestCollector.php +++ b/DataCollector/RequestCollector.php @@ -72,10 +72,12 @@ public function getTime() */ public function parseQuery($request) { - list($endpoint, $params) = explode('?', $request['request']); + list($endpoint, $params) = explode('?', $request['request']['uri']); $request['endpoint'] = $endpoint; $request['params'] = $params; + $request['method'] = $request['request']['method']; + $request['raw_data'] = $request['request']['raw_data']; if (class_exists(VarCloner::class)) { $varCloner = new VarCloner(); diff --git a/Logging/DebugLogger.php b/Logging/DebugLogger.php index 734827d5..e58c19f0 100644 --- a/Logging/DebugLogger.php +++ b/Logging/DebugLogger.php @@ -33,7 +33,7 @@ public function getQueries() /** * {@inheritdoc} */ - public function startRequest($request) + public function startRequest(array $request) { $this->start = microtime(true); $this->queries[++$this->currentQuery] = [ diff --git a/Logging/LoggerChain.php b/Logging/LoggerChain.php deleted file mode 100644 index dc110bfb..00000000 --- a/Logging/LoggerChain.php +++ /dev/null @@ -1,39 +0,0 @@ -loggers, $logger); - } - - /** - * {@inheritdoc} - */ - public function startRequest($request) - { - foreach ($this->loggers as $logger) { - $logger->startRequest($request); - } - } - - /** - * {@inheritdoc} - */ - public function stopRequest() - { - foreach ($this->loggers as $logger) { - $logger->stopRequest(); - } - } -} \ No newline at end of file diff --git a/Logging/SolrLoggerInterface.php b/Logging/SolrLoggerInterface.php index 72bfd8f0..324d1347 100644 --- a/Logging/SolrLoggerInterface.php +++ b/Logging/SolrLoggerInterface.php @@ -7,16 +7,12 @@ interface SolrLoggerInterface /** * Called when the request is started * - * @param string $request - * - * @return mixed + * @param array $request */ - public function startRequest($request); + public function startRequest(array $request); /** * Called when the request has ended - * - * @return mixed */ public function stopRequest(); } \ No newline at end of file diff --git a/Resources/views/Profiler/solr.html.twig b/Resources/views/Profiler/solr.html.twig index a98c899a..7c2036b8 100644 --- a/Resources/views/Profiler/solr.html.twig +++ b/Resources/views/Profiler/solr.html.twig @@ -133,7 +133,9 @@ # Time End Point + Method Parameters + Raw Data @@ -144,6 +146,9 @@ {{ query.endpoint }} + + {{ query.method }} + {% if query.stub is not defined %} {{ query.params | replace({'&': "
"}) | raw }} @@ -151,6 +156,9 @@ {{ profiler_dump(query.stub, 1) }} {% endif %} + +
{{ query.raw_data }}
+ {% endfor %} From e91749ec264b81561144d772fe71230865b59a06 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Thu, 4 Jan 2018 16:13:43 +0100 Subject: [PATCH 19/42] add new "nested" annotation, index nested-documents --- Doctrine/Annotation/AnnotationReader.php | 40 +++++++--- Doctrine/Annotation/Field.php | 5 ++ Doctrine/Annotation/Nested.php | 13 +++ Doctrine/Mapper/Factory/DocumentFactory.php | 53 ++++++++---- Doctrine/Mapper/MetaInformation.php | 23 +++++- Doctrine/Mapper/MetaInformationFactory.php | 21 +++++ Doctrine/Mapper/MetaInformationInterface.php | 5 ++ Solr.php | 4 + .../Annotation/AnnotationReaderTest.php | 15 +++- Tests/Doctrine/Mapper/EntityMapperTest.php | 41 +++------- .../Mapper/MetaInformationFactoryTest.php | 25 ++++++ Tests/Fixtures/EntityNestedProperty.php | 80 +++++++++++++++++++ Tests/Fixtures/NestedEntity.php | 59 ++++++++++++++ 13 files changed, 324 insertions(+), 60 deletions(-) create mode 100644 Doctrine/Annotation/Nested.php create mode 100644 Tests/Fixtures/EntityNestedProperty.php create mode 100644 Tests/Fixtures/NestedEntity.php diff --git a/Doctrine/Annotation/AnnotationReader.php b/Doctrine/Annotation/AnnotationReader.php index 7ad16c66..878dd7a6 100644 --- a/Doctrine/Annotation/AnnotationReader.php +++ b/Doctrine/Annotation/AnnotationReader.php @@ -18,9 +18,9 @@ class AnnotationReader private $entityProperties; const DOCUMENT_CLASS = 'FS\SolrBundle\Doctrine\Annotation\Document'; + const DOCUMENT_NESTED_CLASS = 'FS\SolrBundle\Doctrine\Annotation\Nested'; const FIELD_CLASS = 'FS\SolrBundle\Doctrine\Annotation\Field'; const FIELD_IDENTIFIER_CLASS = 'FS\SolrBundle\Doctrine\Annotation\Id'; - const DOCUMENT_INDEX_CLASS = 'FS\SolrBundle\Doctrine\Annotation\Document'; const SYNCHRONIZATION_FILTER_CLASS = 'FS\SolrBundle\Doctrine\Annotation\SynchronizationFilter'; /** @@ -43,7 +43,7 @@ private function getPropertiesByType($entity, $type) { $properties = $this->readClassProperties($entity); - $fields = array(); + $fields = []; foreach ($properties as $property) { $annotation = $this->reader->getPropertyAnnotation($property, $type); @@ -95,7 +95,7 @@ public function getFields($entity) */ public function getEntityBoost($entity) { - $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_INDEX_CLASS); + $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_CLASS); if (!$annotation instanceof Document) { return 0; @@ -120,7 +120,7 @@ public function getEntityBoost($entity) */ public function getDocumentIndex($entity) { - $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_INDEX_CLASS); + $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_CLASS); if (!$annotation instanceof Document) { return ''; } @@ -178,11 +178,9 @@ public function getFieldMapping($entity) { $fields = $this->getPropertiesByType($entity, self::FIELD_CLASS); - $mapping = array(); + $mapping = []; foreach ($fields as $field) { - if ($field instanceof Field) { - $mapping[$field->getNameWithAlias()] = $field->name; - } + $mapping[$field->getNameWithAlias()] = $field->name; } $id = $this->getIdentifier($entity); @@ -198,9 +196,15 @@ public function getFieldMapping($entity) */ public function hasDocumentDeclaration($entity) { - $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_INDEX_CLASS); + if ($rootDocument = $this->getClassAnnotation($entity, self::DOCUMENT_CLASS)) { + return true; + } - return $annotation !== null; + if ($this->isNested($entity)) { + return true; + } + + return false; } /** @@ -251,6 +255,20 @@ public function isOdm($entity) return true; } + /** + * @param object $entity + * + * @return bool + */ + public function isNested($entity) + { + if ($nestedDocument = $this->getClassAnnotation($entity, self::DOCUMENT_NESTED_CLASS)) { + return true; + } + + return false; + } + /** * @param string $entity * @param string $annotationName @@ -285,7 +303,7 @@ private function readClassProperties($entity) $reflectionClass = new \ReflectionClass($entity); $inheritedProperties = array_merge($this->getParentProperties($reflectionClass), $reflectionClass->getProperties()); - $properties = array(); + $properties = []; foreach ($inheritedProperties as $property) { $properties[$property->getName()] = $property; } diff --git a/Doctrine/Annotation/Field.php b/Doctrine/Annotation/Field.php index e7b7734b..fad8ce82 100644 --- a/Doctrine/Annotation/Field.php +++ b/Doctrine/Annotation/Field.php @@ -37,6 +37,11 @@ class Field extends Annotation */ public $fieldModifier; + /** + * @var string + */ + public $nestedClass; + /** * @var array */ diff --git a/Doctrine/Annotation/Nested.php b/Doctrine/Annotation/Nested.php new file mode 100644 index 00000000..d6d2fd53 --- /dev/null +++ b/Doctrine/Annotation/Nested.php @@ -0,0 +1,13 @@ +getValue(); if ($value instanceof Collection) { - $document->addField($field->getNameWithAlias(), $this->mapCollection($field, $metaInformation->getClassName()), $field->getBoost()); + $this->mapCollectionField($document, $field, $metaInformation->getEntity()); } elseif (is_object($value)) { - $document->addField($field->getNameWithAlias(), $this->mapObject($field), $field->getBoost()); + $document->addField($field->getNameWithAlias(), $this->mapObjectField($field), $field->getBoost()); } else { $document->addField($field->getNameWithAlias(), $field->getValue(), $field->getBoost()); } @@ -82,7 +82,7 @@ public function createDocument(MetaInformationInterface $metaInformation) * * @throws SolrMappingException if getter return value is object */ - private function mapObject(Field $field) + private function mapObjectField(Field $field) { $value = $field->getValue(); $getter = $field->getGetterName(); @@ -96,15 +96,7 @@ private function mapObject(Field $field) return $getterReturnValue; } - $metaInformation = $this->metaInformationFactory->loadInformation($value); - - $field = array(); - $document = $this->createDocument($metaInformation); - foreach ($document as $fieldName => $value) { - $field[$fieldName] = $value; - } - - return $field; + return $this->objectToDocument($value); } /** @@ -148,20 +140,47 @@ private function callGetterMethod($object, $getter) * * @throws SolrMappingException if no getter method was found */ - private function mapCollection(Field $field, $sourceTargetClass) + private function mapCollectionField($document, Field $field, $sourceTargetObject) { /** @var Collection $value */ $value = $field->getValue(); $getter = $field->getGetterName(); - if ($getter == '') { - throw new SolrMappingException(sprintf('No getter method for property "%s" configured in class "%s"', $field->name, $sourceTargetClass)); + + if ($getter != '') { + $value = $this->callGetterMethod($sourceTargetObject, $getter); } - $values = array(); + $values = []; foreach ($value as $relatedObj) { - $values[] = $this->callGetterMethod($relatedObj, $getter); + if (is_object($relatedObj)) { + $values[] = $this->objectToDocument($relatedObj); + } else { + $values[] = $relatedObj; + } } + $document->addField('_childDocuments_', $values, $field->getBoost()); + return $values; } + + /** + * @param mixed $value + * + * @return array + * + * @throws SolrMappingException + */ + private function objectToDocument($value) + { + $metaInformation = $this->metaInformationFactory->loadInformation($value); + + $field = []; + $document = $this->createDocument($metaInformation); + foreach ($document as $fieldName => $value) { + $field[$fieldName] = $value; + } + + return $field; + } } \ No newline at end of file diff --git a/Doctrine/Mapper/MetaInformation.php b/Doctrine/Mapper/MetaInformation.php index 0353fbf1..c960d5b2 100644 --- a/Doctrine/Mapper/MetaInformation.php +++ b/Doctrine/Mapper/MetaInformation.php @@ -75,6 +75,11 @@ class MetaInformation implements MetaInformationInterface */ private $doctrineMapperType; + /** + * @var bool + */ + private $nested; + /** * {@inheritdoc} */ @@ -378,6 +383,22 @@ public function generateDocumentId() throw new SolrMappingException('No identifier is set'); } - return $this->identifier->generateId; + return $this->identifier->generateId || $this->isNested(); + } + + /** + * {@inheritdoc} + */ + public function isNested() + { + return $this->nested; + } + + /** + * @param bool $nested + */ + public function setNested(bool $nested) + { + $this->nested = $nested; } } diff --git a/Doctrine/Mapper/MetaInformationFactory.php b/Doctrine/Mapper/MetaInformationFactory.php index 167b95c0..39aedc73 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -71,6 +71,27 @@ public function loadInformation($entity) $metaInformation->setIndex($this->annotationReader->getDocumentIndex($entity)); $metaInformation->setIsDoctrineEntity($this->isDoctrineEntity($entity)); $metaInformation->setDoctrineMapperType($this->getDoctrineMapperType($entity)); + $metaInformation->setNested($this->annotationReader->isNested($entity)); + + $fields = $this->annotationReader->getFields($entity); + foreach ($fields as $field) { + if (!$field->nestedClass) { + continue; + } + + $nestedObjectMetainformation = $this->loadInformation($field->nestedClass); + + $subentityMapping = []; + $nestedFieldName = $field->name; + foreach ($nestedObjectMetainformation->getFieldMapping() as $documentName => $fieldName) { + $subentityMapping[$nestedFieldName . '.' . $documentName] = $nestedFieldName . '.' . $fieldName; + } + + $rootEntityMapping = $metaInformation->getFieldMapping(); + $subentityMapping = array_merge($subentityMapping, $rootEntityMapping); + unset($subentityMapping[$field->name]); + $metaInformation->setFieldMapping($subentityMapping); + } return $metaInformation; } diff --git a/Doctrine/Mapper/MetaInformationInterface.php b/Doctrine/Mapper/MetaInformationInterface.php index d7a0dbbb..01f425d7 100644 --- a/Doctrine/Mapper/MetaInformationInterface.php +++ b/Doctrine/Mapper/MetaInformationInterface.php @@ -137,4 +137,9 @@ public function getIdentifierFieldName(); * @return string */ public function getDoctrineMapperType(); + + /** + * @return bool + */ + public function isNested(); } \ No newline at end of file diff --git a/Solr.php b/Solr.php index 7d8fa65e..222ad300 100644 --- a/Solr.php +++ b/Solr.php @@ -212,6 +212,10 @@ public function addDocument($entity): bool return false; } + if ($metaInformation->isNested()) { + return false; + } + $event = new Event($this->solrClientCore, $metaInformation); $this->eventManager->dispatch(Events::PRE_INSERT, $event); diff --git a/Tests/Doctrine/Annotation/AnnotationReaderTest.php b/Tests/Doctrine/Annotation/AnnotationReaderTest.php index 8305ad7e..26fdcf54 100644 --- a/Tests/Doctrine/Annotation/AnnotationReaderTest.php +++ b/Tests/Doctrine/Annotation/AnnotationReaderTest.php @@ -195,6 +195,14 @@ public function readAnnotationsFromBaseClass() $this->assertTrue($this->reader->hasDocumentDeclaration(new ChildEntity())); } + /** + * @test + */ + public function readAnnotationsOfNestedObject() + { + $this->assertTrue($this->reader->hasDocumentDeclaration(new NestedObject())); + } + /** * @test */ @@ -300,4 +308,9 @@ class EntityWithObject * @Solr\Field(type="datetime", getter="format('d.m.Y')") */ private $object; -} \ No newline at end of file +} + +/** + * @Solr\Nested() + */ +class NestedObject {} \ No newline at end of file diff --git a/Tests/Doctrine/Mapper/EntityMapperTest.php b/Tests/Doctrine/Mapper/EntityMapperTest.php index 9fa3beab..c79ecfd4 100644 --- a/Tests/Doctrine/Mapper/EntityMapperTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperTest.php @@ -195,24 +195,29 @@ public function testMapEntity_DocumentShouldContainThreeFields() public function mapRelationFieldByGetter() { $entity1 = new ValidTestEntity(); + $entity1->setId(uniqid()); $entity1->setTitle('title 1'); $entity2 = new ValidTestEntity(); + $entity2->setId(uniqid()); $entity2->setTitle('title 2'); $collection = new ArrayCollection(); $collection->add($entity1); $collection->add($entity2); - $metaInformation = MetaTestInformationFactory::getMetaInformation(new ValidTestEntityWithCollection()); + $entity = new ValidTestEntityWithCollection(); + $entity->setTitle($collection); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity); $fields = $metaInformation->getFields(); $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'getTitle')); $metaInformation->setFields($fields); $document = $this->mapper->toDocument($metaInformation); - $this->assertArrayHasKey('collection_ss', $document->getFields()); - $collectionField = $document->getFields()['collection_ss']; + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; $this->assertEquals(2, count($collectionField)); } @@ -220,7 +225,7 @@ public function mapRelationFieldByGetter() /** * @test * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException - * @expectedExceptionMessage No method "unknown()" found in class "DateTime" + * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection" */ public function throwExceptionIfConfiguredGetterDoesNotExists() { @@ -245,8 +250,6 @@ public function throwExceptionIfConfiguredGetterDoesNotExists() */ public function mapRelationFieldAllFields() { - $this->markTestSkipped('sub-documents not yet supported'); - $entity1 = new ValidTestEntity(); $entity1->setId(uniqid()); $entity1->setTitle('title 1'); @@ -269,35 +272,13 @@ public function mapRelationFieldAllFields() $document = $this->mapper->toDocument($metaInformation); - $this->assertArrayHasKey('collection_no_getter_ss', $document->getFields()); - $collectionField = $document->getFields()['collection_no_getter_ss']; + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; $this->assertEquals(2, count($collectionField), 'collection contains 2 fields'); $this->assertEquals(3, count($collectionField[0]), 'field has 2 properties'); } - /** - * @test - * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException - * @expectedExceptionMessage No getter method for property "collection" configured in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection" - */ - public function throwExceptionIfEmbbededObjectsHasNoGetter() - { - $entity2 = new ValidTestEntity(); - $entity2->setTitle('title 2'); - $entity2->setText('text 2'); - - $collection = new ArrayCollection(); - $collection->add($entity2); - - $metaInformation = MetaTestInformationFactory::getMetaInformation(new ValidTestEntityWithCollection()); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection)); - $metaInformation->setFields($fields); - - $this->mapper->toDocument($metaInformation); - } - /** * @test * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException diff --git a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php index fd0101a2..41104ed7 100644 --- a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php +++ b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php @@ -8,6 +8,8 @@ use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; use FS\SolrBundle\Doctrine\Mapper\MetaInformation; use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; +use FS\SolrBundle\Tests\Fixtures\EntityNestedProperty; +use FS\SolrBundle\Tests\Fixtures\NestedEntity; use FS\SolrBundle\Tests\Fixtures\NotIndexedEntity; use FS\SolrBundle\Tests\Fixtures\ValidOdmTestDocument; use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; @@ -179,5 +181,28 @@ public function useCachedEntityInstanceIfItIsSet() $this->assertEquals($metainformation1->getEntity(), $metainformation2->getEntity()); } + + /** + * @test + */ + public function includeNestedFieldsInFieldmapping() + { + $entity = new EntityNestedProperty(); + + $nested1 = new NestedEntity(); + $nested2 = new NestedEntity(); + $entity->setCollection([$nested1, $nested2]); + + $factory = new MetaInformationFactory($this->reader); + $metainformation = $factory->loadInformation($entity); + + $this->assertEquals(4, count($metainformation->getFieldMapping())); + + $this->assertArrayNotHasKey('collection', $metainformation->getFieldMapping()); + $this->assertArrayHasKey('collection.id', $metainformation->getFieldMapping()); + $this->assertArrayHasKey('collection.name_t', $metainformation->getFieldMapping()); + + + } } diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php new file mode 100644 index 00000000..d5f1578c --- /dev/null +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -0,0 +1,80 @@ +id; + } + + /** + * @param mixed $id + */ + public function setId($id) : void + { + $this->id = $id; + } + + /** + * @return string + */ + public function getName() : string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name) : void + { + $this->name = $name; + } + + /** + * @return array + */ + public function getCollection() : array + { + return $this->collection; + } + + /** + * @param array $collection + */ + public function setCollection(array $collection) : void + { + $this->collection = $collection; + } +} \ No newline at end of file diff --git a/Tests/Fixtures/NestedEntity.php b/Tests/Fixtures/NestedEntity.php new file mode 100644 index 00000000..00fab8cb --- /dev/null +++ b/Tests/Fixtures/NestedEntity.php @@ -0,0 +1,59 @@ +id; + } + + /** + * @param mixed $id + */ + public function setId($id) : void + { + $this->id = $id; + } + + /** + * @return string + */ + public function getName() : string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name) : void + { + $this->name = $name; + } + + +} \ No newline at end of file From d2bad9634b5822afd094c338ebc6dd2901b1bb21 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Fri, 5 Jan 2018 08:47:41 +0100 Subject: [PATCH 20/42] query and block-join nested documents --- Query/SolrQuery.php | 29 +++++++++++++++++++++++------ Tests/Query/SolrQueryTest.php | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Query/SolrQuery.php b/Query/SolrQuery.php index c4dfb6cf..50ea22a0 100644 --- a/Query/SolrQuery.php +++ b/Query/SolrQuery.php @@ -10,12 +10,17 @@ class SolrQuery extends AbstractQuery /** * @var array */ - private $mappedFields = array(); + private $mappedFields = []; /** * @var array */ - private $searchTerms = array(); + private $searchTerms = []; + + /** + * @var array + */ + private $childQueries = []; /** * @var bool @@ -132,7 +137,17 @@ public function addSearchTerm($field, $value) } $documentFieldName = $documentFieldsAsValues[$field]; - $this->searchTerms[$documentFieldName] = $value; + if ($position = strpos($field, '.')) { + $nestedFieldMapping = $documentFieldsAsValues[$field]; + + $nestedField = substr($nestedFieldMapping, $position + 1); + + $documentName = $this->getMetaInformation()->getDocumentName(); + $documentFieldName = sprintf('{!parent which="id:%s_*"}%s', $documentName, $nestedField); + $this->childQueries[$documentFieldName] = $value; + } else { + $this->searchTerms[$documentFieldName] = $value; + } return $this; } @@ -169,6 +184,8 @@ public function addFilterQuery($filterQuery) */ public function getQuery() { + $searchTerms = array_merge($this->searchTerms, $this->childQueries); + $keyField = $this->getMetaInformation()->getDocumentKey(); $documentLimitation = $this->createFilterQuery('id')->setQuery('id:'.$keyField.'*'); @@ -182,7 +199,7 @@ public function getQuery() $term = ''; // query all documents if no terms exists - if (count($this->searchTerms) == 0) { + if (count($searchTerms) == 0) { $query = '*:*'; parent::setQuery($query); @@ -195,7 +212,7 @@ public function getQuery() } $termCount = 1; - foreach ($this->searchTerms as $fieldName => $fieldValue) { + foreach ($searchTerms as $fieldName => $fieldValue) { if ($fieldName == 'id') { $this->getFilterQuery('id')->setQuery('id:' . $fieldValue); @@ -209,7 +226,7 @@ public function getQuery() $term .= $fieldName . ':' . $fieldValue; - if ($termCount < count($this->searchTerms)) { + if ($termCount < count($searchTerms)) { $term .= ' ' . $logicOperator . ' '; } diff --git a/Tests/Query/SolrQueryTest.php b/Tests/Query/SolrQueryTest.php index 2c4bf5b7..7ca1ed61 100644 --- a/Tests/Query/SolrQueryTest.php +++ b/Tests/Query/SolrQueryTest.php @@ -5,6 +5,7 @@ use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; use FS\SolrBundle\Doctrine\Annotation\Id; use FS\SolrBundle\Doctrine\Mapper\MetaInformation; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; use FS\SolrBundle\Query\Exception\UnknownFieldException; use FS\SolrBundle\Query\SolrQuery; use FS\SolrBundle\SolrInterface; @@ -252,4 +253,24 @@ public function doNotAddIdFieldTwice() $this->assertEquals($expected, $query->getQuery()); $this->assertEquals('id:post_1', $query->getFilterQuery('id')->getQuery()); } + + /** + * @test + */ + public function generateQueryForNestedDocuments() + { + $mapping = [ + 'id' => 'id', + 'title_s' => 'title', + 'collection.id' => 'collection.id', + 'collection.name_s' => 'collection.name' + ]; + + $query = $this->createQueryWithFieldMapping(); + $query->setMappedFields($mapping); + $query->addSearchTerm('collection.name', 'test'); + $query->addSearchTerm('title', 'test post'); + + $this->assertEquals('title_s:"test post" OR {!parent which="id:post_*"}name_s:test', $query->getQuery()); + } } From 2ac1b9c724550a6935b306be355e9ddd9c6a0ec1 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 11:27:24 +0100 Subject: [PATCH 21/42] delete root-document + nested documents --- Doctrine/AbstractIndexingListener.php | 12 ++++ Doctrine/Annotation/AnnotationReader.php | 2 +- Doctrine/Mapper/MetaInformation.php | 2 +- .../ORM/Listener/EntityIndexerSubscriber.php | 60 ++++++++++++++++--- Solr.php | 10 ++-- composer.json | 3 +- 6 files changed, 72 insertions(+), 17 deletions(-) diff --git a/Doctrine/AbstractIndexingListener.php b/Doctrine/AbstractIndexingListener.php index 29b1268d..50dff63b 100644 --- a/Doctrine/AbstractIndexingListener.php +++ b/Doctrine/AbstractIndexingListener.php @@ -61,4 +61,16 @@ protected function hasChanged($doctrineChangeSet, $entity) return count($documentChangeSet) > 0; } + + /** + * @param object $entity + * + * @return bool + */ + protected function isNested($entity) + { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + return $metaInformation->isNested(); + } } \ No newline at end of file diff --git a/Doctrine/Annotation/AnnotationReader.php b/Doctrine/Annotation/AnnotationReader.php index 878dd7a6..1ff98c89 100644 --- a/Doctrine/Annotation/AnnotationReader.php +++ b/Doctrine/Annotation/AnnotationReader.php @@ -122,7 +122,7 @@ public function getDocumentIndex($entity) { $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_CLASS); if (!$annotation instanceof Document) { - return ''; + return null; } $indexHandler = $annotation->indexHandler; diff --git a/Doctrine/Mapper/MetaInformation.php b/Doctrine/Mapper/MetaInformation.php index c960d5b2..220b97db 100644 --- a/Doctrine/Mapper/MetaInformation.php +++ b/Doctrine/Mapper/MetaInformation.php @@ -383,7 +383,7 @@ public function generateDocumentId() throw new SolrMappingException('No identifier is set'); } - return $this->identifier->generateId || $this->isNested(); + return $this->identifier->generateId; } /** diff --git a/Doctrine/ORM/Listener/EntityIndexerSubscriber.php b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php index 29135335..c026370d 100644 --- a/Doctrine/ORM/Listener/EntityIndexerSubscriber.php +++ b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php @@ -4,6 +4,7 @@ use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LifecycleEventArgs; +use Doctrine\ORM\Event\PostFlushEventArgs; use FS\SolrBundle\Doctrine\AbstractIndexingListener; use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; use FS\SolrBundle\SolrInterface; @@ -11,13 +12,27 @@ class EntityIndexerSubscriber extends AbstractIndexingListener implements EventSubscriber { + /** + * @var array + */ + private $persistedEntities = []; + + /** + * @var array + */ + private $deletedRootEntities = []; + + /** + * @var array + */ + private $deletedNestedEntities = []; /** * {@inheritdoc} */ public function getSubscribedEvents() { - return array('postUpdate', 'postPersist', 'preRemove'); + return array('postUpdate', 'postPersist', 'preRemove', 'postFlush'); } /** @@ -46,11 +61,7 @@ public function postPersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); - try { - $this->solr->addDocument($entity); - } catch (\Exception $e) { - $this->logger->debug($e->getMessage()); - } + $this->persistedEntities[] = $entity; } /** @@ -60,10 +71,41 @@ public function preRemove(LifecycleEventArgs $args) { $entity = $args->getEntity(); - try { + if ($this->isNested($entity)) { + $this->deletedNestedEntities[] = clone $entity; + } else { + $entity = clone $entity; + $this->deletedRootEntities[] = $this->emptyCollections($entity); + } + } + + private function emptyCollections($object) + { + if (method_exists($object, 'setTags')) { + $object->setTags([]); + } + + return $object; + } + + /** + * @param PostFlushEventArgs $eventArgs + */ + public function postFlush(PostFlushEventArgs $eventArgs) + { + foreach ($this->persistedEntities as $entity) { + $this->solr->addDocument($entity); + } + $this->persistedEntities = []; + + foreach ($this->deletedRootEntities as $entity) { + $this->solr->removeDocument($entity); + } + $this->deletedRootEntities = []; + + foreach ($this->deletedNestedEntities as $entity) { $this->solr->removeDocument($entity); - } catch (\Exception $e) { - $this->logger->debug($e->getMessage()); } + $this->deletedNestedEntities = []; } } \ No newline at end of file diff --git a/Solr.php b/Solr.php index 222ad300..9c303a93 100644 --- a/Solr.php +++ b/Solr.php @@ -175,21 +175,21 @@ public function createQueryBuilder($entity): QueryBuilderInterface */ public function removeDocument($entity) { - $metaInformations = $this->metaInformationFactory->loadInformation($entity); + $metaInformation = $this->metaInformationFactory->loadInformation($entity); - $event = new Event($this->solrClientCore, $metaInformations); + $event = new Event($this->solrClientCore, $metaInformation); $this->eventManager->dispatch(Events::PRE_DELETE, $event); - if ($document = $this->entityMapper->toDocument($metaInformations)) { + if ($document = $this->entityMapper->toDocument($metaInformation)) { try { - $indexName = $metaInformations->getIndex(); + $indexName = $metaInformation->getIndex(); $client = new SolariumMulticoreClient($this->solrClientCore); $client->delete($document, $indexName); } catch (\Exception $e) { - $errorEvent = new ErrorEvent(null, $metaInformations, 'delete-document', $event); + $errorEvent = new ErrorEvent(null, $metaInformation, 'delete-document', $event); $errorEvent->setException($e); $this->eventManager->dispatch(Events::ERROR, $errorEvent); diff --git a/composer.json b/composer.json index 1cd6f4a4..bee792f5 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "symfony/config": "^2.3|^3.0|^4.0", "symfony/doctrine-bridge": "^2.3|^3.0|^4.0", "minimalcode/search": "^1.0", - "ramsey/uuid": "^3.5" + "ramsey/uuid": "^3.5", + "myclabs/deep-copy": "^1.7" }, "require-dev": { "behat/behat": "^3.1", From 4d99bae2581f373028521c575a01c0ebea9013b4 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 13:30:21 +0100 Subject: [PATCH 22/42] use deep-copy for object cloning --- .../ORM/Listener/EntityIndexerSubscriber.php | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Doctrine/ORM/Listener/EntityIndexerSubscriber.php b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php index c026370d..660e7ed4 100644 --- a/Doctrine/ORM/Listener/EntityIndexerSubscriber.php +++ b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php @@ -2,13 +2,13 @@ namespace FS\SolrBundle\Doctrine\ORM\Listener; +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; +use DeepCopy\Matcher\PropertyTypeMatcher; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Event\PostFlushEventArgs; use FS\SolrBundle\Doctrine\AbstractIndexingListener; -use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; -use FS\SolrBundle\SolrInterface; -use Psr\Log\LoggerInterface; class EntityIndexerSubscriber extends AbstractIndexingListener implements EventSubscriber { @@ -32,7 +32,7 @@ class EntityIndexerSubscriber extends AbstractIndexingListener implements EventS */ public function getSubscribedEvents() { - return array('postUpdate', 'postPersist', 'preRemove', 'postFlush'); + return ['postUpdate', 'postPersist', 'preRemove', 'postFlush']; } /** @@ -72,20 +72,23 @@ public function preRemove(LifecycleEventArgs $args) $entity = $args->getEntity(); if ($this->isNested($entity)) { - $this->deletedNestedEntities[] = clone $entity; + $this->deletedNestedEntities[] = $this->emptyCollections($entity); } else { - $entity = clone $entity; $this->deletedRootEntities[] = $this->emptyCollections($entity); } } + /** + * @param object $object + * + * @return object + */ private function emptyCollections($object) { - if (method_exists($object, 'setTags')) { - $object->setTags([]); - } + $deepcopy = new DeepCopy(); + $deepcopy->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); - return $object; + return $deepcopy->copy($object); } /** From c4f196ff7fc889ee6c592b1728565d6daced5ee7 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 15:08:10 +0100 Subject: [PATCH 23/42] add tests for indexer --- .../Listener/EntityIndexerSubscriberTest.php | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Tests/Doctrine/ORM/Listener/EntityIndexerSubscriberTest.php diff --git a/Tests/Doctrine/ORM/Listener/EntityIndexerSubscriberTest.php b/Tests/Doctrine/ORM/Listener/EntityIndexerSubscriberTest.php new file mode 100644 index 00000000..289c8152 --- /dev/null +++ b/Tests/Doctrine/ORM/Listener/EntityIndexerSubscriberTest.php @@ -0,0 +1,119 @@ +logger = $this->createMock(LoggerInterface::class); + $this->solr = $this->createMock(SolrInterface::class); + $this->metaInformationFactory = new MetaInformationFactory(new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); + + $this->subscriber = new EntityIndexerSubscriber($this->solr, $this->metaInformationFactory, $this->logger); + } + + /** + * @test + */ + public function separteDeletedRootEntitiesFromNested() + { + $nested = new NestedEntity(); + $nested->setId(uniqid()); + + $entity = new ValidTestEntityWithCollection(); + $entity->setId(uniqid()); + $entity->setCollection(new ArrayCollection([$nested])); + + $objectManager = $this->createMock(ObjectManager::class); + + $this->solr->expects($this->at(0)) + ->method('removeDocument') + ->with($this->callback(function(ValidTestEntityWithCollection $entity) { + if (count($entity->getCollection())) { + return false; + } + + return true; + })); + + $this->solr->expects($this->at(1)) + ->method('removeDocument') + ->with($this->callback(function($entity) { + if (!$entity instanceof NestedEntity) { + return false; + } + + return true; + })); + + $deleteRootEntityEvent = new LifecycleEventArgs($entity, $objectManager); + $this->subscriber->preRemove($deleteRootEntityEvent); + + $deleteNestedEntityEvent = new LifecycleEventArgs($nested, $objectManager); + $this->subscriber->preRemove($deleteNestedEntityEvent); + + $entityManager = $this->createMock(EntityManagerInterface::class); + + $this->subscriber->postFlush(new PostFlushEventArgs($entityManager)); + } + + /** + * @test + */ + public function indexOnlyModifiedEntites() + { + $changedEntity = new ValidTestEntityWithCollection(); + $this->solr->expects($this->once()) + ->method('updateDocument') + ->with($changedEntity); + + $unitOfWork = $this->createMock(UnitOfWork::class); + $unitOfWork->expects($this->at(0)) + ->method('getEntityChangeSet') + ->willReturn(['title' => 'value']); + + $unitOfWork->expects($this->at(1)) + ->method('getEntityChangeSet') + ->willReturn([]); + + $objectManager = $this->createMock(EntityManagerInterface::class); + $objectManager->expects($this->any()) + ->method('getUnitOfWork') + ->willReturn($unitOfWork); + + $updateEntityEvent1 = new LifecycleEventArgs($changedEntity, $objectManager); + + $unmodifiedEntity = new ValidTestEntityWithCollection(); + $updateEntityEvent2 = new LifecycleEventArgs($unmodifiedEntity, $objectManager); + + $this->subscriber->postUpdate($updateEntityEvent1); + $this->subscriber->postUpdate($updateEntityEvent2); + } +} From 37604f2009cd0bd402e23223f5e2c102e84a7761 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 15:54:34 +0100 Subject: [PATCH 24/42] require version with php5 support --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bee792f5..4aa2c613 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "symfony/doctrine-bridge": "^2.3|^3.0|^4.0", "minimalcode/search": "^1.0", "ramsey/uuid": "^3.5", - "myclabs/deep-copy": "^1.7" + "myclabs/deep-copy": "^1.7", + "doctrine/annotations": "^1.4" }, "require-dev": { "behat/behat": "^3.1", From 850ef4c3ce69658f9b2a6c2d3afac2915ba63939 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 16:16:09 +0100 Subject: [PATCH 25/42] remove void return type --- Tests/Fixtures/NestedEntity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Fixtures/NestedEntity.php b/Tests/Fixtures/NestedEntity.php index 00fab8cb..d2383f3f 100644 --- a/Tests/Fixtures/NestedEntity.php +++ b/Tests/Fixtures/NestedEntity.php @@ -34,7 +34,7 @@ public function getId() /** * @param mixed $id */ - public function setId($id) : void + public function setId($id) { $this->id = $id; } @@ -50,7 +50,7 @@ public function getName() : string /** * @param string $name */ - public function setName(string $name) : void + public function setName(string $name) { $this->name = $name; } From c55e9e4ab745253a1cb128ed32b1975ce8b6109c Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 17 Jan 2018 16:23:25 +0100 Subject: [PATCH 26/42] remove void return type --- Tests/Fixtures/EntityNestedProperty.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php index d5f1578c..eb07a83f 100644 --- a/Tests/Fixtures/EntityNestedProperty.php +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -41,7 +41,7 @@ public function getId() /** * @param mixed $id */ - public function setId($id) : void + public function setId($id) { $this->id = $id; } @@ -57,7 +57,7 @@ public function getName() : string /** * @param string $name */ - public function setName(string $name) : void + public function setName(string $name) { $this->name = $name; } @@ -73,7 +73,7 @@ public function getCollection() : array /** * @param array $collection */ - public function setCollection(array $collection) : void + public function setCollection(array $collection) { $this->collection = $collection; } From 653c1fda82c3ae848a2ff645ad64ea115b65834e Mon Sep 17 00:00:00 2001 From: Florian Semm Date: Thu, 18 Jan 2018 10:31:28 +0100 Subject: [PATCH 27/42] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4aa2c613..498d648c 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "symfony/doctrine-bridge": "^2.3|^3.0|^4.0", "minimalcode/search": "^1.0", "ramsey/uuid": "^3.5", - "myclabs/deep-copy": "^1.7", + "myclabs/deep-copy": "^1.6", "doctrine/annotations": "^1.4" }, "require-dev": { From b57b3292fa4cb7d7e61be897c9707ace441df7ec Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 12:11:24 +0100 Subject: [PATCH 28/42] do not index nested documents directly --- Command/SynchronizeIndexCommand.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Command/SynchronizeIndexCommand.php b/Command/SynchronizeIndexCommand.php index 28b63bb2..0dfdbec4 100644 --- a/Command/SynchronizeIndexCommand.php +++ b/Command/SynchronizeIndexCommand.php @@ -95,7 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $i++; } - $entities = $repository->findBy(array(), null, $batchSize, $offset); + $entities = $repository->findBy([], null, $batchSize, $offset); try { $solr->synchronizeIndex($entities); @@ -141,16 +141,20 @@ private function getObjectManager($entityClassname) private function getIndexableEntities($entity = null) { if ($entity) { - return array($entity); + return [$entity]; } - $entities = array(); + $entities = []; $namespaces = $this->getContainer()->get('solr.doctrine.classnameresolver.known_entity_namespaces'); $metaInformationFactory = $this->getContainer()->get('solr.meta.information.factory'); foreach ($namespaces->getEntityClassnames() as $classname) { try { $metaInformation = $metaInformationFactory->loadInformation($classname); + if ($metaInformation->isNested()) { + continue; + } + array_push($entities, $metaInformation->getClassName()); } catch (SolrMappingException $e) { continue; From c6676774452d0f162d07473a17c4f6607e411e6c Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 12:28:30 +0100 Subject: [PATCH 29/42] show if document is nested --- Command/ShowSchemaCommand.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Command/ShowSchemaCommand.php b/Command/ShowSchemaCommand.php index a9c01642..0bac58ae 100644 --- a/Command/ShowSchemaCommand.php +++ b/Command/ShowSchemaCommand.php @@ -35,7 +35,11 @@ protected function execute(InputInterface $input, OutputInterface $output) continue; } - $output->writeln(sprintf('%s', $classname)); + $nested = ''; + if ($metaInformation->isNested()) { + $nested = '(nested)'; + } + $output->writeln(sprintf('%s %s', $classname, $nested)); $output->writeln(sprintf('Documentname: %s', $metaInformation->getDocumentName())); $output->writeln(sprintf('Document Boost: %s', $metaInformation->getBoost()?$metaInformation->getBoost(): '-')); From c282b85a627e26099fa382c55fb7947a6abc68fe Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 14:24:52 +0100 Subject: [PATCH 30/42] show mapping information about nested fields --- Command/ShowSchemaCommand.php | 97 +++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/Command/ShowSchemaCommand.php b/Command/ShowSchemaCommand.php index 0bac58ae..2516c28a 100644 --- a/Command/ShowSchemaCommand.php +++ b/Command/ShowSchemaCommand.php @@ -2,6 +2,7 @@ namespace FS\SolrBundle\Command; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; use FS\SolrBundle\Doctrine\Mapper\SolrMappingException; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Helper\Table; @@ -30,8 +31,11 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($namespaces->getEntityClassnames() as $classname) { try { $metaInformation = $metaInformationFactory->loadInformation($classname); + + if ($metaInformation->isNested()) { + continue; + } } catch (SolrMappingException $e) { - $output->writeln(sprintf('%s', $e->getMessage())); continue; } @@ -43,22 +47,95 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(sprintf('Documentname: %s', $metaInformation->getDocumentName())); $output->writeln(sprintf('Document Boost: %s', $metaInformation->getBoost()?$metaInformation->getBoost(): '-')); - $table = new Table($output); - $table->setHeaders(array('Property', 'Document Fieldname', 'Boost')); + $simpleFields = $this->getSimpleFields($metaInformation); - foreach ($metaInformation->getFieldMapping() as $documentField => $property) { - $field = $metaInformation->getField($documentField); - - if ($field === null) { - continue; + $rows = []; + foreach ($simpleFields as $documentField => $property) { + if ($field = $metaInformation->getField($documentField)) { + $rows[] = [$property, $documentField, $field->boost]; } + } + $this->renderTable($output, $rows); + + $nestedFields = $this->getNestedFields($metaInformation); + if (count($nestedFields) == 0) { + return; + } + + $output->writeln(sprintf('Fields (%s) with nested documents', count($nestedFields))); - $table->addRow(array($property, $documentField, $field->boost)); + foreach ($nestedFields as $idField) { + $propertyName = substr($idField, 0, strpos($idField, '.')); + + if ($nestedField = $metaInformation->getField($propertyName)) { + $output->writeln(sprintf('Field %s contains nested class %s', $propertyName, $nestedField->nestedClass)); + + $nestedDocument = $metaInformationFactory->loadInformation($nestedField->nestedClass); + $rows = []; + foreach ($nestedDocument->getFieldMapping() as $documentField => $property) { + $field = $nestedDocument->getField($documentField); + + if ($field === null) { + continue; + } + + $rows[] = [$property, $documentField, $field->boost]; + } + + $this->renderTable($output, $rows); + } } - $table->render(); + } } + /** + * @param OutputInterface $output + * @param array $rows + */ + private function renderTable(OutputInterface $output, array $rows) + { + $table = new Table($output); + $table->setHeaders(array('Property', 'Document Fieldname', 'Boost')); + $table->setRows($rows); + + $table->render(); + } + + /** + * @param MetaInformationInterface $metaInformation + * + * @return array + */ + private function getSimpleFields(MetaInformationInterface $metaInformation) + { + $simpleFields = array_filter($metaInformation->getFieldMapping(), function ($field) { + if (strpos($field, '.') === false) { + return true; + } + + return false; + }); + + return $simpleFields; + } + + /** + * @param MetaInformationInterface $metaInformation + * + * @return array + */ + protected function getNestedFields(MetaInformationInterface $metaInformation) + { + $complexFields = array_filter($metaInformation->getFieldMapping(), function ($field) { + if (strpos($field, '.id') !== false) { + return true; + } + return false; + }); + + return $complexFields; + } } \ No newline at end of file From 74976b336203b88dcd5ab806d9d68b90e293d903 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 15:39:42 +0100 Subject: [PATCH 31/42] move mapper tests with complex values to dedicated test-suite --- .../Mapper/EntityMapperObjectRelationTest.php | 301 ++++++++++++++++++ Tests/Doctrine/Mapper/EntityMapperTest.php | 223 ------------- 2 files changed, 301 insertions(+), 223 deletions(-) create mode 100644 Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php new file mode 100644 index 00000000..49cc8b7a --- /dev/null +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -0,0 +1,301 @@ +doctrineHydrator = $this->createMock(HydratorInterface::class); + $this->indexHydrator = $this->createMock(HydratorInterface::class); + $this->metaInformationFactory = new MetaInformationFactory(new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); + + $this->mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator, $this->metaInformationFactory); + } + + /** + * @test + */ + public function mapRelationFieldByGetter() + { + $collectionItem1 = new ValidTestEntity(); + $collectionItem1->setId(uniqid()); + $collectionItem1->setTitle('title 1'); + $collectionItem1->setText('text 1'); + + $collectionItem2 = new ValidTestEntity(); + $collectionItem2->setId(uniqid()); + $collectionItem2->setTitle('title 2'); + $collectionItem2->setText('text 2'); + + $collection = new ArrayCollection(); + $collection->add($collectionItem1); + $collection->add($collectionItem2); + + $entity = new ValidTestEntityWithCollection(); + $entity->setTitle($collection); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity); + $fields = $metaInformation->getFields(); + $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'getTitle')); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; + + $this->assertCollectionItemsMappedProperly($collectionField); + } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection" + */ + public function throwExceptionIfConfiguredGetterDoesNotExists() + { + $entity1 = new \DateTime('+2 days'); + + $entity2 = new \DateTime('+1 day'); + + $collection = new ArrayCollection(); + $collection->add($entity1); + $collection->add($entity2); + + $metaInformation = MetaTestInformationFactory::getMetaInformation(new ValidTestEntityWithCollection()); + $fields = $metaInformation->getFields(); + $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'unknown(\'d.m.Y\')')); + $metaInformation->setFields($fields); + + $this->mapper->toDocument($metaInformation); + } + + /** + * @test + */ + public function mapRelationFieldAllFields() + { + $collectionItem1 = new ValidTestEntity(); + $collectionItem1->setId(uniqid()); + $collectionItem1->setTitle('title 1'); + $collectionItem1->setText('text 1'); + + $collectionItem2 = new ValidTestEntity(); + $collectionItem2->setId(uniqid()); + $collectionItem2->setTitle('title 2'); + $collectionItem2->setText('text 2'); + + $collection = new ArrayCollection(); + $collection->add($collectionItem1); + $collection->add($collectionItem2); + + $entity = new ValidTestEntityWithCollection(); + $entity->setId(uniqid()); + $entity->setCollectionNoGetter($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; + + $this->assertCollectionItemsMappedProperly($collectionField); + } + + /** + * @test + */ + public function mapRelationField_AllFields() + { + $entity2 = new ValidTestEntity(); + $entity2->setTitle('embbeded object'); + + $entity1 = new ValidTestEntityWithRelation(); + $entity1->setTitle('title 1'); + $entity1->setText('text 1'); + $entity1->setRelation($entity2); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); + $fields = $metaInformation->getFields(); + $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity1)); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('relation_ss', $document->getFields()); + $collectionField = $document->getFields()['relation_ss']; + + $this->assertEquals(4, count($collectionField), 'collection contains 4 fields'); + } + + /** + * @test + */ + public function mapRelationField_Getter() + { + $entity2 = new ValidTestEntity(); + $entity2->setTitle('embedded object'); + + $entity1 = new ValidTestEntityWithRelation(); + $entity1->setTitle('title 1'); + $entity1->setText('text 1'); + $entity1->setRelation($entity2); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); + $fields = $metaInformation->getFields(); + $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity2, 'getter'=>'getTitle')); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('relation_ss', $document->getFields()); + $collectionField = $document->getFields()['relation_ss']; + + $this->assertEquals('embedded object', $collectionField); + } + + /** + * @test + */ + public function callGetterWithParameter_ObjectProperty() + { + $entity1 = new ValidTestEntity(); + $date = new \DateTime(); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); + $metaInformation->setFields(array( + new Field(array('name' => 'created_at', 'type' => 'datetime', 'boost' => '1', 'value' => $date, 'getter' => "format('d.m.Y')")) + )); + + $fields = $metaInformation->getFields(); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $this->assertArrayHasKey('created_at_dt', $fields); + $this->assertEquals($date->format('d.m.Y'), $fields['created_at_dt']); + } + + /** + * @test + */ + public function callGetterWithParameters_ObjectProperty() + { + $entity1 = new ValidTestEntity(); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); + $metaInformation->setFields(array( + new Field(array('name' => 'test_field', 'type' => 'datetime', 'boost' => '1', 'value' => new TestObject(), 'getter' => "testGetter('string3', 'string1', 'string')")) + )); + + $fields = $metaInformation->getFields(); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $this->assertArrayHasKey('test_field_dt', $fields); + $this->assertEquals(array('string3', 'string1', 'string'), $fields['test_field_dt']); + } + + /** + * @test + */ + public function callGetterWithParameter_SimpleProperty() + { + $data = ['key' => 'value']; + + $date = new \DateTime(); + $entity1 = new ValidTestEntity(); + $entity1->setId(uniqid()); + $entity1->setCreatedAt($date); + $entity1->setComplexDataType(json_encode($data)); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity1); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $this->assertArrayHasKey('created_at_dt', $fields); + $this->assertEquals($date->format('d.m.Y'), $fields['created_at_dt']); + $this->assertArrayHasKey('complex_data_type', $fields); + + $this->assertEquals($data, $fields['complex_data_type']); + } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage The configured getter "asString" in "FS\SolrBundle\Tests\Doctrine\Mapper\TestObject" must return a string or array, got object + */ + public function callGetterWithObjectAsReturnValue() + { + $entity1 = new ValidTestEntity(); + + $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); + $metaInformation->setFields(array( + new Field(array('name' => 'test_field', 'type' => 'datetime', 'boost' => '1', 'value' => new TestObject(), 'getter' => "asString")) + )); + + $fields = $metaInformation->getFields(); + $metaInformation->setFields($fields); + + $this->mapper->toDocument($metaInformation); + } + + /** + * @param array $collectionField + */ + private function assertCollectionItemsMappedProperly($collectionField) + { + $this->assertEquals(2, count($collectionField), 'should be 2 collection items'); + + foreach ($collectionField as $item) { + $this->assertArrayHasKey('id', $item); + $this->assertArrayHasKey('title', $item); + $this->assertEquals(3, count($item), 'field has 3 properties'); + } + } +} + +class TestObject { + public function testGetter($para1, $para2, $para3) + { + return array($para1, $para2, $para3); + } + + public function asString() + { + return $this; + } +} \ No newline at end of file diff --git a/Tests/Doctrine/Mapper/EntityMapperTest.php b/Tests/Doctrine/Mapper/EntityMapperTest.php index 2de1f80f..7cd18916 100644 --- a/Tests/Doctrine/Mapper/EntityMapperTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperTest.php @@ -189,96 +189,6 @@ public function testMapEntity_DocumentShouldContainThreeFields() $this->assertArrayHasKey('created_at_dt', $document); } - /** - * @test - */ - public function mapRelationFieldByGetter() - { - $entity1 = new ValidTestEntity(); - $entity1->setId(uniqid()); - $entity1->setTitle('title 1'); - - $entity2 = new ValidTestEntity(); - $entity2->setId(uniqid()); - $entity2->setTitle('title 2'); - - $collection = new ArrayCollection(); - $collection->add($entity1); - $collection->add($entity2); - - $entity = new ValidTestEntityWithCollection(); - $entity->setTitle($collection); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'getTitle')); - $metaInformation->setFields($fields); - - $document = $this->mapper->toDocument($metaInformation); - - $this->assertArrayHasKey('_childDocuments_', $document->getFields()); - $collectionField = $document->getFields()['_childDocuments_']; - - $this->assertEquals(2, count($collectionField)); - } - - /** - * @test - * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException - * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection" - */ - public function throwExceptionIfConfiguredGetterDoesNotExists() - { - $entity1 = new \DateTime('+2 days'); - - $entity2 = new \DateTime('+1 day'); - - $collection = new ArrayCollection(); - $collection->add($entity1); - $collection->add($entity2); - - $metaInformation = MetaTestInformationFactory::getMetaInformation(new ValidTestEntityWithCollection()); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'unknown(\'d.m.Y\')')); - $metaInformation->setFields($fields); - - $this->mapper->toDocument($metaInformation); - } - - /** - * @test - */ - public function mapRelationFieldAllFields() - { - $entity1 = new ValidTestEntity(); - $entity1->setId(uniqid()); - $entity1->setTitle('title 1'); - $entity1->setText('text 1'); - - $entity2 = new ValidTestEntity(); - $entity2->setId(uniqid()); - $entity2->setTitle('title 2'); - $entity2->setText('text 2'); - - $collection = new ArrayCollection(); - $collection->add($entity1); - $collection->add($entity2); - - $entity = new ValidTestEntityWithCollection(); - $entity->setId(uniqid()); - $entity->setCollectionNoGetter($collection); - - $metaInformation = $this->metaInformationFactory->loadInformation($entity); - - $document = $this->mapper->toDocument($metaInformation); - - $this->assertArrayHasKey('_childDocuments_', $document->getFields()); - $collectionField = $document->getFields()['_childDocuments_']; - - $this->assertEquals(2, count($collectionField), 'collection contains 2 fields'); - $this->assertEquals(3, count($collectionField[0]), 'field has 2 properties'); - } - /** * @test * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException @@ -293,127 +203,6 @@ public function throwExceptionIfEntityHasNoId() $this->mapper->toDocument($metaInformation); } - /** - * @test - */ - public function mapRelationField_AllFields() - { - $entity2 = new ValidTestEntity(); - $entity2->setTitle('embbeded object'); - - $entity1 = new ValidTestEntityWithRelation(); - $entity1->setTitle('title 1'); - $entity1->setText('text 1'); - $entity1->setRelation($entity2); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity1)); - $metaInformation->setFields($fields); - - $document = $this->mapper->toDocument($metaInformation); - - $this->assertArrayHasKey('relation_ss', $document->getFields()); - $collectionField = $document->getFields()['relation_ss']; - - $this->assertEquals(4, count($collectionField), 'collection contains 4 fields'); - } - - /** - * @test - */ - public function mapRelationField_Getter() - { - $entity2 = new ValidTestEntity(); - $entity2->setTitle('embedded object'); - - $entity1 = new ValidTestEntityWithRelation(); - $entity1->setTitle('title 1'); - $entity1->setText('text 1'); - $entity1->setRelation($entity2); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity2, 'getter'=>'getTitle')); - $metaInformation->setFields($fields); - - $document = $this->mapper->toDocument($metaInformation); - - $this->assertArrayHasKey('relation_ss', $document->getFields()); - $collectionField = $document->getFields()['relation_ss']; - - $this->assertEquals('embedded object', $collectionField); - } - - /** - * @test - */ - public function callGetterWithParameter() - { - $data = ['key' => 'value']; - - $date = new \DateTime(); - $entity1 = new ValidTestEntity(); - $entity1->setId(uniqid()); - $entity1->setCreatedAt($date); - $entity1->setComplexDataType(json_encode($data)); - - $metaInformation = $this->metaInformationFactory->loadInformation($entity1); - - $document = $this->mapper->toDocument($metaInformation); - - $fields = $document->getFields(); - - $this->assertArrayHasKey('created_at_dt', $fields); - $this->assertEquals($date->format('d.m.Y'), $fields['created_at_dt']); - $this->assertArrayHasKey('complex_data_type', $fields); - - $this->assertEquals($data, $fields['complex_data_type']); - } - - /** - * @test - */ - public function callGetterWithParameters() - { - $entity1 = new ValidTestEntity(); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $metaInformation->setFields(array( - new Field(array('name' => 'test_field', 'type' => 'datetime', 'boost' => '1', 'value' => new TestObject(), 'getter' => "testGetter('string3', 'string1', 'string')")) - )); - - $fields = $metaInformation->getFields(); - $metaInformation->setFields($fields); - - $document = $this->mapper->toDocument($metaInformation); - - $fields = $document->getFields(); - - $this->assertArrayHasKey('test_field_dt', $fields); - $this->assertEquals(array('string3', 'string1', 'string'), $fields['test_field_dt']); - } - - /** - * @test - * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException - * @expectedExceptionMessage The configured getter "asString" in "FS\SolrBundle\Tests\Doctrine\Mapper\TestObject" must return a string or array, got object - */ - public function callGetterWithObjectAsReturnValue() - { - $entity1 = new ValidTestEntity(); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $metaInformation->setFields(array( - new Field(array('name' => 'test_field', 'type' => 'datetime', 'boost' => '1', 'value' => new TestObject(), 'getter' => "asString")) - )); - - $fields = $metaInformation->getFields(); - $metaInformation->setFields($fields); - - $this->mapper->toDocument($metaInformation); - } - private function setupOrmManager($entity, $expectedEntityId) { $repository = $this->createMock(ObjectRepository::class); @@ -470,16 +259,4 @@ class PlainObject * @Solr\Id */ private $id; -} - -class TestObject { - public function testGetter($para1, $para2, $para3) - { - return array($para1, $para2, $para3); - } - - public function asString() - { - return $this; - } } \ No newline at end of file From febeed81e3060c56ddc66dfd87970154181b015b Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 16:32:31 +0100 Subject: [PATCH 32/42] refactor documentation --- README.md | 86 +----------------------- Resources/doc/index_relations.md | 110 ++++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index fdcdb84f..7abfcc60 100644 --- a/README.md +++ b/README.md @@ -242,70 +242,13 @@ Solr supports partial updates of fields in an existing document. Supported value - remove (multivalue field only, removes a value(s) from existing list) - inc (integer field only) +### `nestedClass` property -### Object relations - -Indexing relations works in simplified way. Related entities will not be indexed as a new document, but only as a searchable value. -Related entities do not need a `@Solr\Document` annotation. - -#### ManyToOne relation - -```php -/** - * @var Category - * - * @Solr\Field(type="string", getter="getTitle") - * - * @ORM\ManyToOne(targetEntity="Acme\DemoBundle\Entity\Category", inversedBy="posts", cascade={"persist"}) - * @ORM\JoinColumn(name="category_id", referencedColumnName="id") - */ -private $category; -``` - -Related entity: - -```php -class Category -{ - /** - * @return string - */ - public function getTitle() - { - return $this->title; - } -} -``` +Set this property if you want to index collections with nested Objects. -#### OneToMany relation - -To index a set of objects it is important to use the fieldtype `strings`. - -```php -/** - * @var Tag[] - * - * @Solr\Field(type="strings", getter="getName") - * - * @ORM\OneToMany(targetEntity="Acme\DemoBundle\Entity\Tag", mappedBy="post", cascade={"persist"}) - */ -private $tags; -``` -Related entity: -```php -class Tag -{ - /** - * @return string - */ - public function getName() - { - return $this->name; - } -} -``` +### Object relations [For more information read the more detailed "How to index relation" guide](Resources/doc/index_relations.md) @@ -414,29 +357,6 @@ class YourRepository extends Repository ``` - -### Define Result-Mapping - -To narrow the mapping, you can use the `addField()` method. - -```php -$query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); -$query->addSearchTerm('title', 'my title'); -$query->addField('id'); -$query->addField('text'); - -$result = $query->getResult(); -``` - -In this case, only the `id` and `text` fields will be mapped (addField()), `title` and created_at` fields will be -empty. If nothing was found $result is empty. - -By default, the result set contains 10 rows. You can increase this value: - -```php -$query->setRows(1000000); -``` - ### Configure HydrationModes HydrationMode tells the bundle how to create an entity from a document. diff --git a/Resources/doc/index_relations.md b/Resources/doc/index_relations.md index 8d3e7de5..8c432088 100644 --- a/Resources/doc/index_relations.md +++ b/Resources/doc/index_relations.md @@ -10,8 +10,6 @@ Given you have the following entity with a ManyToOne relation to `Category`. use FS\SolrBundle\Doctrine\Annotation as Solr; /** - * Post - * * @Solr\Document() * * @ORM\Table() @@ -22,9 +20,7 @@ class Post /** * @var integer * - * @ORM\Column(name="id", type="integer") - * @ORM\Id - * @ORM\GeneratedValue(strategy="AUTO") + * orm stuff * * @Solr\Id */ @@ -53,6 +49,13 @@ class Post } ``` +You have now different ways to index the `category` relation: + +- flat string representation +- full object + +## Flat string representation + The important configuration is `@Solr\Field(type="string", getter="getTitle")`. This tells Solr to call `Category::getTitle()` when the `Post` is indexed. ```php @@ -69,20 +72,7 @@ $em->persist($post); $em->flush(); ``` -The index data would look something like this: - -```json -"docs": [ - { - "id": "post_1", - "title_s": "a post title", - "category_s": "post category #1", - "_version_": 1529771282767282200 - } -] -``` - -The result of search-queries like this +### Quering the relation ```php $posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOneBy(array( @@ -90,12 +80,17 @@ $posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOn )); ``` -contain a `Post` entity with a `Category` entity. The indexed data `post category #1` was replaced by DB reference. - # Index OneToMany relation Given you have the following `Post` entity with a OneToMany relation to `Tag`. +Again you can index the collection in two ways: + +- flat strings representation +- full objects + +## flat strings representation + ```php get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOn 'tags' => 'tag #1' )); ``` - \ No newline at end of file + +## Index full objects + +Post entity: + +```php + /** + * @Solr\Field(type="strings", nestedClass="Acme\DemoBundle\Entity\Tag") + * + * @ORM\OneToMany(targetEntity="Acme\DemoBundle\Entity\Tag", mappedBy="post", cascade={"persist"}) + */ + private $tags; +``` + +Mark the `Tag` entity as Nested + +```php +/** + * Tag + * + * @Solr\Nested() + * + * @ORM\Table() + * @ORM\Entity + */ +class Tag +{ + /** + * @var integer + * + * @Solr\Id + * + * orm stuff + */ + private $id; + + /** + * @var string + * + * @Solr\Field(type="string") + * + * @ORM\Column(name="name", type="string", length=255) + */ + private $name; + + // getter and setter +} +``` + +## Querying the collection + +Now `Post` can be searched like this + +```php +$posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOneBy(array( + 'tags.name' => 'tag #1' +)); +``` + From af6ca0476bbaa123eb21820b2788eb8039c48315 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 17:30:40 +0100 Subject: [PATCH 33/42] index one-to-one relation --- Doctrine/Mapper/Factory/DocumentFactory.php | 4 ++- .../Mapper/EntityMapperObjectRelationTest.php | 30 +++++++++++++++++++ .../Mapper/MetaInformationFactoryTest.php | 2 -- Tests/Fixtures/EntityNestedProperty.php | 25 ++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Doctrine/Mapper/Factory/DocumentFactory.php b/Doctrine/Mapper/Factory/DocumentFactory.php index 9d515d28..47e25ae5 100644 --- a/Doctrine/Mapper/Factory/DocumentFactory.php +++ b/Doctrine/Mapper/Factory/DocumentFactory.php @@ -61,7 +61,9 @@ public function createDocument(MetaInformationInterface $metaInformation) $fieldValue = $field->getValue(); if ($fieldValue instanceof Collection) { $this->mapCollectionField($document, $field, $metaInformation->getEntity()); - } elseif (is_object($fieldValue)) { + } else if (is_object($fieldValue) && $field->nestedClass) { + $document->addField('_childDocuments_', [$this->objectToDocument($fieldValue)], $field->getBoost()); + } else if (is_object($fieldValue) && !$field->nestedClass) { $document->addField($field->getNameWithAlias(), $this->mapObjectField($field), $field->getBoost()); } else if ($field->getter && $fieldValue) { $getterValue = $this->callGetterMethod($metaInformation->getEntity(), $field->getGetterName()); diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index 49cc8b7a..2918dd17 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -8,6 +8,8 @@ use FS\SolrBundle\Doctrine\Hydration\HydratorInterface; use FS\SolrBundle\Doctrine\Mapper\EntityMapper; use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; +use FS\SolrBundle\Tests\Fixtures\EntityNestedProperty; +use FS\SolrBundle\Tests\Fixtures\NestedEntity; use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection; use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithRelation; @@ -154,6 +156,34 @@ public function mapRelationField_AllFields() $this->assertEquals(4, count($collectionField), 'collection contains 4 fields'); } + /** + * @test + */ + public function mapEntityWithRelation_singleObject() + { + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + + $nested1 = new NestedEntity(); + $nested1->setId(uniqid()); + $nested1->setName('nested document'); + + $entity->setNestedProperty($nested1); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $this->assertArrayHasKey('_childDocuments_', $fields); + + $subDocument = $fields['_childDocuments_'][0]; + + $this->assertArrayHasKey('id', $subDocument); + $this->assertArrayHasKey('name_t', $subDocument); + } + /** * @test */ diff --git a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php index ea313ee0..6fadad25 100644 --- a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php +++ b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php @@ -196,8 +196,6 @@ public function includeNestedFieldsInFieldmapping() $factory = new MetaInformationFactory($this->reader); $metainformation = $factory->loadInformation($entity); - $this->assertEquals(4, count($metainformation->getFieldMapping())); - $this->assertArrayNotHasKey('collection', $metainformation->getFieldMapping()); $this->assertArrayHasKey('collection.id', $metainformation->getFieldMapping()); $this->assertArrayHasKey('collection.name_t', $metainformation->getFieldMapping()); diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php index eb07a83f..e19b4186 100644 --- a/Tests/Fixtures/EntityNestedProperty.php +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -30,6 +30,13 @@ class EntityNestedProperty */ private $collection; + /** + * @var object + * + * @Solr\Field(nestedClass="FS\SolrBundle\Tests\Fixtures\NestedEntity") + */ + private $nestedProperty; + /** * @return mixed */ @@ -77,4 +84,22 @@ public function setCollection(array $collection) { $this->collection = $collection; } + + /** + * @return object + */ + public function getNestedProperty() + { + return $this->nestedProperty; + } + + /** + * @param object $nestedProperty + */ + public function setNestedProperty($nestedProperty) + { + $this->nestedProperty = $nestedProperty; + } + + } \ No newline at end of file From a3139c976c4197a31fb283e977ce9b1456fd45b7 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 17:34:35 +0100 Subject: [PATCH 34/42] add edge-case --- .../Mapper/EntityMapperObjectRelationTest.php | 35 +++++++++++++++++++ Tests/Fixtures/EntityNestedProperty.php | 8 ++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index 2918dd17..affab92a 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -184,6 +184,41 @@ public function mapEntityWithRelation_singleObject() $this->assertArrayHasKey('name_t', $subDocument); } + /** + * @test + */ + public function indexEntityMultipleRelations() + { + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + + $nested1 = new NestedEntity(); + $nested1->setId(uniqid()); + $nested1->setName('nested document'); + + $entity->setNestedProperty($nested1); + + $collectionItem1 = new NestedEntity(); + $collectionItem1->setId(uniqid()); + $collectionItem1->setName('collection item 1'); + + $collectionItem2 = new NestedEntity(); + $collectionItem2->setId(uniqid()); + $collectionItem2->setName('collection item 2'); + + $collection = new ArrayCollection([$collectionItem1, $collectionItem2]); + + $entity->setCollection($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $this->assertEquals(3, count($fields['_childDocuments_'])); + } + /** * @test */ diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php index e19b4186..c0b643e7 100644 --- a/Tests/Fixtures/EntityNestedProperty.php +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -56,7 +56,7 @@ public function setId($id) /** * @return string */ - public function getName() : string + public function getName() { return $this->name; } @@ -64,7 +64,7 @@ public function getName() : string /** * @param string $name */ - public function setName(string $name) + public function setName($name) { $this->name = $name; } @@ -72,7 +72,7 @@ public function setName(string $name) /** * @return array */ - public function getCollection() : array + public function getCollection() { return $this->collection; } @@ -80,7 +80,7 @@ public function getCollection() : array /** * @param array $collection */ - public function setCollection(array $collection) + public function setCollection($collection) { $this->collection = $collection; } From 65ed3416943edf077ce1cbafe257e953cbb4a418 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 17:40:40 +0100 Subject: [PATCH 35/42] remove old testcase --- .../Mapper/EntityMapperObjectRelationTest.php | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index affab92a..b1fe9118 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -130,32 +130,6 @@ public function mapRelationFieldAllFields() $this->assertCollectionItemsMappedProperly($collectionField); } - /** - * @test - */ - public function mapRelationField_AllFields() - { - $entity2 = new ValidTestEntity(); - $entity2->setTitle('embbeded object'); - - $entity1 = new ValidTestEntityWithRelation(); - $entity1->setTitle('title 1'); - $entity1->setText('text 1'); - $entity1->setRelation($entity2); - - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity1)); - $metaInformation->setFields($fields); - - $document = $this->mapper->toDocument($metaInformation); - - $this->assertArrayHasKey('relation_ss', $document->getFields()); - $collectionField = $document->getFields()['relation_ss']; - - $this->assertEquals(4, count($collectionField), 'collection contains 4 fields'); - } - /** * @test */ From a53c8865dfff8bbdcf92161b05482c742b5c294e Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 22 Jan 2018 18:17:13 +0100 Subject: [PATCH 36/42] cleanup fixtures classes --- Doctrine/Mapper/Factory/DocumentFactory.php | 2 +- .../Mapper/EntityMapperObjectRelationTest.php | 112 ++++++++---------- Tests/Fixtures/EntityNestedProperty.php | 77 ++++++++---- Tests/Fixtures/NestedEntity.php | 4 +- 4 files changed, 108 insertions(+), 87 deletions(-) diff --git a/Doctrine/Mapper/Factory/DocumentFactory.php b/Doctrine/Mapper/Factory/DocumentFactory.php index 47e25ae5..d2346732 100644 --- a/Doctrine/Mapper/Factory/DocumentFactory.php +++ b/Doctrine/Mapper/Factory/DocumentFactory.php @@ -59,7 +59,7 @@ public function createDocument(MetaInformationInterface $metaInformation) } $fieldValue = $field->getValue(); - if ($fieldValue instanceof Collection) { + if ($fieldValue instanceof Collection && $field->nestedClass) { $this->mapCollectionField($document, $field, $metaInformation->getEntity()); } else if (is_object($fieldValue) && $field->nestedClass) { $document->addField('_childDocuments_', [$this->objectToDocument($fieldValue)], $field->getBoost()); diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index b1fe9118..7536dbb0 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -44,55 +44,44 @@ public function setUp() */ public function mapRelationFieldByGetter() { - $collectionItem1 = new ValidTestEntity(); + $collectionItem1 = new NestedEntity(); $collectionItem1->setId(uniqid()); - $collectionItem1->setTitle('title 1'); - $collectionItem1->setText('text 1'); + $collectionItem1->setName('title 1'); - $collectionItem2 = new ValidTestEntity(); + $collectionItem2 = new NestedEntity(); $collectionItem2->setId(uniqid()); - $collectionItem2->setTitle('title 2'); - $collectionItem2->setText('text 2'); + $collectionItem2->setName('title 2'); - $collection = new ArrayCollection(); - $collection->add($collectionItem1); - $collection->add($collectionItem2); + $collection = new ArrayCollection([$collectionItem1, $collectionItem2]); - $entity = new ValidTestEntityWithCollection(); - $entity->setTitle($collection); + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollectionValidGetter($collection); - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'getTitle')); - $metaInformation->setFields($fields); + $metaInformation = $this->metaInformationFactory->loadInformation($entity); $document = $this->mapper->toDocument($metaInformation); $this->assertArrayHasKey('_childDocuments_', $document->getFields()); $collectionField = $document->getFields()['_childDocuments_']; - $this->assertCollectionItemsMappedProperly($collectionField); + $this->assertCollectionItemsMappedProperly($collectionField, 1); } /** * @test * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException - * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection" + * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\EntityNestedProperty" */ public function throwExceptionIfConfiguredGetterDoesNotExists() { - $entity1 = new \DateTime('+2 days'); + $collection = new ArrayCollection([new \DateTime(), new \DateTime()]); - $entity2 = new \DateTime('+1 day'); - - $collection = new ArrayCollection(); - $collection->add($entity1); - $collection->add($entity2); + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollectionInvalidGetter($collection); - $metaInformation = MetaTestInformationFactory::getMetaInformation(new ValidTestEntityWithCollection()); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'collection', 'type' => 'strings', 'boost' => '1', 'value' => $collection, 'getter'=>'unknown(\'d.m.Y\')')); - $metaInformation->setFields($fields); + $metaInformation = $this->metaInformationFactory->loadInformation($entity); $this->mapper->toDocument($metaInformation); } @@ -102,23 +91,19 @@ public function throwExceptionIfConfiguredGetterDoesNotExists() */ public function mapRelationFieldAllFields() { - $collectionItem1 = new ValidTestEntity(); + $collectionItem1 = new NestedEntity(); $collectionItem1->setId(uniqid()); - $collectionItem1->setTitle('title 1'); - $collectionItem1->setText('text 1'); + $collectionItem1->setName('title 1'); - $collectionItem2 = new ValidTestEntity(); + $collectionItem2 = new NestedEntity(); $collectionItem2->setId(uniqid()); - $collectionItem2->setTitle('title 2'); - $collectionItem2->setText('text 2'); + $collectionItem2->setName('title 2'); - $collection = new ArrayCollection(); - $collection->add($collectionItem1); - $collection->add($collectionItem2); + $collection = new ArrayCollection([$collectionItem1, $collectionItem2]); - $entity = new ValidTestEntityWithCollection(); + $entity = new EntityNestedProperty(); $entity->setId(uniqid()); - $entity->setCollectionNoGetter($collection); + $entity->setCollection($collection); $metaInformation = $this->metaInformationFactory->loadInformation($entity); @@ -127,7 +112,7 @@ public function mapRelationFieldAllFields() $this->assertArrayHasKey('_childDocuments_', $document->getFields()); $collectionField = $document->getFields()['_childDocuments_']; - $this->assertCollectionItemsMappedProperly($collectionField); + $this->assertCollectionItemsMappedProperly($collectionField, 2); } /** @@ -198,25 +183,24 @@ public function indexEntityMultipleRelations() */ public function mapRelationField_Getter() { - $entity2 = new ValidTestEntity(); - $entity2->setTitle('embedded object'); + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); - $entity1 = new ValidTestEntityWithRelation(); - $entity1->setTitle('title 1'); - $entity1->setText('text 1'); - $entity1->setRelation($entity2); + $object = new NestedEntity(); + $object->setId(uniqid()); + $object->setName('nested entity'); - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $fields = $metaInformation->getFields(); - $fields[] = new Field(array('name' => 'relation', 'type' => 'strings', 'boost' => '1', 'value' => $entity2, 'getter'=>'getTitle')); - $metaInformation->setFields($fields); + $entity->setSimpleGetter($object); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); $document = $this->mapper->toDocument($metaInformation); - $this->assertArrayHasKey('relation_ss', $document->getFields()); - $collectionField = $document->getFields()['relation_ss']; + $this->assertArrayHasKey('simple_getter_s', $document->getFields()); + + $collectionField = $document->getFields()['simple_getter_s']; - $this->assertEquals('embedded object', $collectionField); + $this->assertEquals('nested entity', $collectionField); } /** @@ -224,13 +208,13 @@ public function mapRelationField_Getter() */ public function callGetterWithParameter_ObjectProperty() { - $entity1 = new ValidTestEntity(); $date = new \DateTime(); - $metaInformation = MetaTestInformationFactory::getMetaInformation($entity1); - $metaInformation->setFields(array( - new Field(array('name' => 'created_at', 'type' => 'datetime', 'boost' => '1', 'value' => $date, 'getter' => "format('d.m.Y')")) - )); + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setGetterWithParameters($date); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); $fields = $metaInformation->getFields(); $metaInformation->setFields($fields); @@ -238,9 +222,8 @@ public function callGetterWithParameter_ObjectProperty() $document = $this->mapper->toDocument($metaInformation); $fields = $document->getFields(); - - $this->assertArrayHasKey('created_at_dt', $fields); - $this->assertEquals($date->format('d.m.Y'), $fields['created_at_dt']); + $this->assertArrayHasKey('getter_with_parameters_dt', $fields); + $this->assertEquals($date->format('d.m.Y'), $fields['getter_with_parameters_dt']); } /** @@ -314,15 +297,16 @@ public function callGetterWithObjectAsReturnValue() /** * @param array $collectionField + * @param int $expectedItems */ - private function assertCollectionItemsMappedProperly($collectionField) + private function assertCollectionItemsMappedProperly($collectionField, $expectedItems) { - $this->assertEquals(2, count($collectionField), 'should be 2 collection items'); + $this->assertEquals($expectedItems, count($collectionField), 'should be 2 collection items'); foreach ($collectionField as $item) { $this->assertArrayHasKey('id', $item); - $this->assertArrayHasKey('title', $item); - $this->assertEquals(3, count($item), 'field has 3 properties'); + $this->assertArrayHasKey('name_t', $item); + $this->assertEquals(2, count($item), 'field has 2 properties'); } } } diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php index c0b643e7..f1fe0f02 100644 --- a/Tests/Fixtures/EntityNestedProperty.php +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -30,6 +30,20 @@ class EntityNestedProperty */ private $collection; + /** + * @var array + * + * @Solr\Field(nestedClass="FS\SolrBundle\Tests\Fixtures\NestedEntity", getter="sliceCollection") + */ + private $collectionValidGetter; + + /** + * @var array + * + * @Solr\Field(nestedClass="FS\SolrBundle\Tests\Fixtures\NestedEntity", getter="unknown") + */ + private $collectionInvalidGetter; + /** * @var object * @@ -37,6 +51,16 @@ class EntityNestedProperty */ private $nestedProperty; + /** + * @Solr\Field(type="datetime", getter="format('d.m.Y')") + */ + private $getterWithParameters; + + /** + * @Solr\Field(type="string", getter="getName") + */ + private $simpleGetter; + /** * @return mixed */ @@ -52,13 +76,10 @@ public function setId($id) { $this->id = $id; } - - /** - * @return string - */ - public function getName() + + public function sliceCollection() { - return $this->name; + return [$this->collectionValidGetter[0]]; } /** @@ -70,12 +91,6 @@ public function setName($name) } /** - * @return array - */ - public function getCollection() - { - return $this->collection; - } /** * @param array $collection @@ -86,20 +101,42 @@ public function setCollection($collection) } /** - * @return object + * @param object $nestedProperty */ - public function getNestedProperty() + public function setNestedProperty($nestedProperty) { - return $this->nestedProperty; + $this->nestedProperty = $nestedProperty; } /** - * @param object $nestedProperty + * @param array $collectionValidGetter */ - public function setNestedProperty($nestedProperty) + public function setCollectionValidGetter($collectionValidGetter) { - $this->nestedProperty = $nestedProperty; + $this->collectionValidGetter = $collectionValidGetter; + } + + /** + * @param array $collectionInvalidGetter + */ + public function setCollectionInvalidGetter($collectionInvalidGetter) + { + $this->collectionInvalidGetter = $collectionInvalidGetter; + } + + /** + * @param mixed $objectToSimpleFormat + */ + public function setGetterWithParameters($getterWithParameters) + { + $this->getterWithParameters = $getterWithParameters; + } + + /** + * @param mixed $simpleGetter + */ + public function setSimpleGetter($simpleGetter) + { + $this->simpleGetter = $simpleGetter; } - - } \ No newline at end of file diff --git a/Tests/Fixtures/NestedEntity.php b/Tests/Fixtures/NestedEntity.php index d2383f3f..e03df8b7 100644 --- a/Tests/Fixtures/NestedEntity.php +++ b/Tests/Fixtures/NestedEntity.php @@ -42,7 +42,7 @@ public function setId($id) /** * @return string */ - public function getName() : string + public function getName() { return $this->name; } @@ -50,7 +50,7 @@ public function getName() : string /** * @param string $name */ - public function setName(string $name) + public function setName($name) { $this->name = $name; } From 7114856805744dc23399515b948ab5c208f8bede Mon Sep 17 00:00:00 2001 From: floriansemm Date: Tue, 23 Jan 2018 08:24:53 +0100 Subject: [PATCH 37/42] cleanup fixtures classes --- Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index 7536dbb0..c714cd5e 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -259,7 +259,6 @@ public function callGetterWithParameter_SimpleProperty() $date = new \DateTime(); $entity1 = new ValidTestEntity(); $entity1->setId(uniqid()); - $entity1->setCreatedAt($date); $entity1->setComplexDataType(json_encode($data)); $metaInformation = $this->metaInformationFactory->loadInformation($entity1); @@ -268,8 +267,6 @@ public function callGetterWithParameter_SimpleProperty() $fields = $document->getFields(); - $this->assertArrayHasKey('created_at_dt', $fields); - $this->assertEquals($date->format('d.m.Y'), $fields['created_at_dt']); $this->assertArrayHasKey('complex_data_type', $fields); $this->assertEquals($data, $fields['complex_data_type']); From 6cc41710c53c82288fcd196c5ec213841bfa475c Mon Sep 17 00:00:00 2001 From: floriansemm Date: Tue, 23 Jan 2018 08:25:02 +0100 Subject: [PATCH 38/42] remove old code --- Doctrine/Mapper/Factory/DocumentFactory.php | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Doctrine/Mapper/Factory/DocumentFactory.php b/Doctrine/Mapper/Factory/DocumentFactory.php index d2346732..cae46e0e 100644 --- a/Doctrine/Mapper/Factory/DocumentFactory.php +++ b/Doctrine/Mapper/Factory/DocumentFactory.php @@ -61,14 +61,14 @@ public function createDocument(MetaInformationInterface $metaInformation) $fieldValue = $field->getValue(); if ($fieldValue instanceof Collection && $field->nestedClass) { $this->mapCollectionField($document, $field, $metaInformation->getEntity()); - } else if (is_object($fieldValue) && $field->nestedClass) { + } else if (is_object($fieldValue) && $field->nestedClass) { // index sinsgle object as nested child-document $document->addField('_childDocuments_', [$this->objectToDocument($fieldValue)], $field->getBoost()); - } else if (is_object($fieldValue) && !$field->nestedClass) { + } else if (is_object($fieldValue) && !$field->nestedClass) { // index object as "flat" string, call getter $document->addField($field->getNameWithAlias(), $this->mapObjectField($field), $field->getBoost()); - } else if ($field->getter && $fieldValue) { + } else if ($field->getter && $fieldValue) { // call getter to transform data (json to array, etc.) $getterValue = $this->callGetterMethod($metaInformation->getEntity(), $field->getGetterName()); $document->addField($field->getNameWithAlias(), $getterValue, $field->getBoost()); - } else { + } else { // field contains simple data-type $document->addField($field->getNameWithAlias(), $fieldValue, $field->getBoost()); } @@ -91,17 +91,17 @@ private function mapObjectField(Field $field) { $value = $field->getValue(); $getter = $field->getGetterName(); - if (!empty($getter)) { - $getterReturnValue = $this->callGetterMethod($value, $getter); - - if (is_object($getterReturnValue)) { - throw new SolrMappingException(sprintf('The configured getter "%s" in "%s" must return a string or array, got object', $getter, get_class($value))); - } + if (empty($getter)) { + throw new SolrMappingException(sprintf('Please configure a getter for property "%s" in class "%s"', $field->name, get_class($value))); + } + + $getterReturnValue = $this->callGetterMethod($value, $getter); - return $getterReturnValue; + if (is_object($getterReturnValue)) { + throw new SolrMappingException(sprintf('The configured getter "%s" in "%s" must return a string or array, got object', $getter, get_class($value))); } - return $this->objectToDocument($value); + return $getterReturnValue; } /** From 737587571d5a4f4e03bd9db38b5997eebe17a3d3 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Wed, 7 Feb 2018 15:42:14 +0100 Subject: [PATCH 39/42] add point field configuration --- Doctrine/Annotation/Field.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doctrine/Annotation/Field.php b/Doctrine/Annotation/Field.php index fad8ce82..3f87f08b 100644 --- a/Doctrine/Annotation/Field.php +++ b/Doctrine/Annotation/Field.php @@ -59,7 +59,8 @@ class Field extends Annotation 'long' => '_l', 'float' => '_f', 'double' => '_d', - 'datetime' => '_dt' + 'datetime' => '_dt', + 'point' => '_p' ); /** From 8aaa4dc8ba2e4ca71790329b7b82efc1ab965597 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Thu, 8 Feb 2018 09:29:23 +0100 Subject: [PATCH 40/42] methods with field annotation will be used as virtual-field --- Doctrine/Annotation/AnnotationReader.php | 33 +++++++++++++++++ Doctrine/Mapper/MetaInformationFactory.php | 4 +- .../Annotation/AnnotationReaderTest.php | 21 ++++++++++- .../Mapper/EntityMapperObjectRelationTest.php | 37 +++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/Doctrine/Annotation/AnnotationReader.php b/Doctrine/Annotation/AnnotationReader.php index 1ff98c89..d8c8ed64 100644 --- a/Doctrine/Annotation/AnnotationReader.php +++ b/Doctrine/Annotation/AnnotationReader.php @@ -4,6 +4,7 @@ use Doctrine\Common\Annotations\Annotation; use Doctrine\Common\Annotations\Reader; +use FS\SolrBundle\Doctrine\Mapper\SolrMappingException; class AnnotationReader { @@ -86,6 +87,38 @@ public function getFields($entity) return $this->getPropertiesByType($entity, self::FIELD_CLASS); } + /** + * @param object $entity + * + * @return array + * + * @throws \ReflectionException + */ + public function getMethods($entity) + { + $reflectionClass = new \ReflectionClass($entity); + + $methods = []; + foreach ($reflectionClass->getMethods() as $method) { + /** @var Field $annotation */ + $annotation = $this->reader->getMethodAnnotation($method, self::FIELD_CLASS); + + if ($annotation === null) { + continue; + } + + $annotation->value = $method->invoke($entity); + + if ($annotation->name == '') { + throw new SolrMappingException(sprintf('Please configure a field-name for method "%s" with field-annotation in class "%s"', $method->getName(), get_class($entity))); + } + + $methods[] = $annotation; + } + + return $methods; + } + /** * @param object $entity * diff --git a/Doctrine/Mapper/MetaInformationFactory.php b/Doctrine/Mapper/MetaInformationFactory.php index 39aedc73..071b219f 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -58,12 +58,14 @@ public function loadInformation($entity) throw new SolrMappingException(sprintf('no declaration for document found in entity %s', $className)); } + $fields = array_merge($this->annotationReader->getFields($entity), $this->annotationReader->getMethods($entity)); + $metaInformation = new MetaInformation(); $metaInformation->setEntity($entity); $metaInformation->setClassName($className); $metaInformation->setDocumentName($this->getDocumentName($className)); $metaInformation->setFieldMapping($this->annotationReader->getFieldMapping($entity)); - $metaInformation->setFields($this->annotationReader->getFields($entity)); + $metaInformation->setFields($fields); $metaInformation->setRepository($this->annotationReader->getRepository($entity)); $metaInformation->setIdentifier($this->annotationReader->getIdentifier($entity)); $metaInformation->setBoost($this->annotationReader->getEntityBoost($entity)); diff --git a/Tests/Doctrine/Annotation/AnnotationReaderTest.php b/Tests/Doctrine/Annotation/AnnotationReaderTest.php index 82282fa5..508a4677 100644 --- a/Tests/Doctrine/Annotation/AnnotationReaderTest.php +++ b/Tests/Doctrine/Annotation/AnnotationReaderTest.php @@ -259,6 +259,15 @@ public function checkIfValidDocumentIsDoctrineDocument() { $this->assertTrue($this->reader->isOdm(new ValidOdmTestDocument()), 'is a doctrine document'); } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + */ + public function methodWithAnnotationMustHaveAField() + { + $this->reader->getMethods(new EntityMissingNameProperty()); + } } use FS\SolrBundle\Doctrine\Annotation as Solr; @@ -313,4 +322,14 @@ class EntityWithObject /** * @Solr\Nested() */ -class NestedObject {} \ No newline at end of file +class NestedObject {} + +/** @Solr\Document() */ +class EntityMissingNameProperty { + + /** @Solr\Field(type="string") */ + public function getPropertyValue2() + { + return 1234; + } +} \ No newline at end of file diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php index c714cd5e..83148f5b 100644 --- a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -14,6 +14,7 @@ use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection; use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithRelation; use FS\SolrBundle\Tests\Util\MetaTestInformationFactory; +use FS\SolrBundle\Doctrine\Annotation as Solr; class EntityMapperObjectRelationTest extends \PHPUnit_Framework_TestCase { @@ -292,6 +293,21 @@ public function callGetterWithObjectAsReturnValue() $this->mapper->toDocument($metaInformation); } + /** + * @test + */ + public function callGetterToRetrieveFieldValue() + { + $metainformation = $this->metaInformationFactory->loadInformation(new TestObject()); + + $document = $this->mapper->toDocument($metainformation); + + $fields = $document->getFields(); + + $this->assertArrayHasKey('property_s', $fields); + $this->assertEquals(1234, $fields['property_s']); + } + /** * @param array $collectionField * @param int $expectedItems @@ -308,7 +324,28 @@ private function assertCollectionItemsMappedProperly($collectionField, $expected } } +/** @Solr\Document() */ class TestObject { + + /** @Solr\Id */ + private $id; + + public function __construct() + { + $this->id = uniqid(); + } + + public function getId() + { + return $this->id; + } + + /** @Solr\Field(type="string", name="property") */ + public function getPropertyValue() + { + return 1234; + } + public function testGetter($para1, $para2, $para3) { return array($para1, $para2, $para3); From 96fce0754bc422f347f0a40c5a1b8905cc68e147 Mon Sep 17 00:00:00 2001 From: floriansemm Date: Thu, 25 Oct 2018 16:37:34 +0200 Subject: [PATCH 41/42] add tests, remove string escaping in case of nested field query --- Query/SolrQuery.php | 5 ++- Tests/Query/SolrQueryTest.php | 2 +- Tests/Repository/RepositoryTest.php | 57 +++++++++++++++++------------ 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Query/SolrQuery.php b/Query/SolrQuery.php index 50ea22a0..d0b3c0b6 100644 --- a/Query/SolrQuery.php +++ b/Query/SolrQuery.php @@ -144,7 +144,10 @@ public function addSearchTerm($field, $value) $documentName = $this->getMetaInformation()->getDocumentName(); $documentFieldName = sprintf('{!parent which="id:%s_*"}%s', $documentName, $nestedField); - $this->childQueries[$documentFieldName] = $value; + $childFilterPhrase = str_replace('"', '*', $value); + $childFilterPhrase = str_replace(' ', '*', $value); + $childFilterPhrase = str_replace('\*', '*', $value); + $this->childQueries[$documentFieldName] = $childFilterPhrase; } else { $this->searchTerms[$documentFieldName] = $value; } diff --git a/Tests/Query/SolrQueryTest.php b/Tests/Query/SolrQueryTest.php index 7ca1ed61..908f9b56 100644 --- a/Tests/Query/SolrQueryTest.php +++ b/Tests/Query/SolrQueryTest.php @@ -268,7 +268,7 @@ public function generateQueryForNestedDocuments() $query = $this->createQueryWithFieldMapping(); $query->setMappedFields($mapping); - $query->addSearchTerm('collection.name', 'test'); + $query->addSearchTerm('collection.name', 'test*bar'); $query->addSearchTerm('title', 'test post'); $this->assertEquals('title_s:"test post" OR {!parent which="id:post_*"}name_s:test', $query->getQuery()); diff --git a/Tests/Repository/RepositoryTest.php b/Tests/Repository/RepositoryTest.php index 3d394478..e58e0145 100644 --- a/Tests/Repository/RepositoryTest.php +++ b/Tests/Repository/RepositoryTest.php @@ -10,6 +10,7 @@ use FS\SolrBundle\Query\AbstractQuery; use FS\SolrBundle\Query\FindByDocumentNameQuery; use FS\SolrBundle\Query\FindByIdentifierQuery; +use FS\SolrBundle\Tests\Fixtures\EntityNestedProperty; use FS\SolrBundle\Tests\SolrClientFake; use FS\SolrBundle\Tests\Util\MetaTestInformationFactory; use FS\SolrBundle\Tests\Util\CommandFactoryStub; @@ -28,9 +29,15 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase */ private $metaInformationFactory; + private $mapper; + protected function setUp() { $this->metaInformationFactory = new MetaInformationFactory($reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); + $this->mapper = $this->createMock(EntityMapperInterface::class); + $this->mapper->expects($this->once()) + ->method('setHydrationMode') + ->with(HydrationModes::HYDRATE_DOCTRINE); } public function testFind_DocumentIsKnown() @@ -41,15 +48,10 @@ public function testFind_DocumentIsKnown() $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $mapper = $this->createMock(EntityMapperInterface::class); - $mapper->expects($this->once()) - ->method('setHydrationMode') - ->with(HydrationModes::HYDRATE_DOCTRINE); - $entity = new ValidTestEntity(); $solr = new SolrClientFake(); - $solr->mapper = $mapper; + $solr->mapper = $this->mapper; $solr->response = array($entity); $repo = new Repository($solr, $metaInformation); @@ -66,15 +68,10 @@ public function testFindAll() { $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $mapper = $this->createMock(EntityMapperInterface::class); - $mapper->expects($this->once()) - ->method('setHydrationMode') - ->with(HydrationModes::HYDRATE_DOCTRINE); - $entity = new ValidTestEntity(); $solr = new SolrClientFake(); - $solr->mapper = $mapper; + $solr->mapper = $this->mapper; $solr->response = array($entity); $repo = new Repository($solr, $metaInformation); @@ -96,15 +93,10 @@ public function testFindBy() $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $mapper = $this->createMock(EntityMapperInterface::class); - $mapper->expects($this->once()) - ->method('setHydrationMode') - ->with(HydrationModes::HYDRATE_DOCTRINE); - $entity = new ValidTestEntity(); $solr = new SolrClientFake(); - $solr->mapper = $mapper; + $solr->mapper = $this->mapper; $solr->response = array($entity); $solr->metaFactory = $this->metaInformationFactory; @@ -128,15 +120,10 @@ public function testFindOneBy() $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $mapper = $this->createMock(EntityMapperInterface::class); - $mapper->expects($this->once()) - ->method('setHydrationMode') - ->with(HydrationModes::HYDRATE_DOCTRINE); - $entity = new ValidTestEntity(); $solr = new SolrClientFake(); - $solr->mapper = $mapper; + $solr->mapper = $this->mapper; $solr->response = array($entity); $solr->metaFactory = $this->metaInformationFactory; @@ -151,5 +138,27 @@ public function testFindOneBy() $this->assertEquals('id:validtestentity_*', $solr->query->getFilterQuery('id')->getQuery()); } + /** + * @test + */ + public function findOneByNestedField() + { + $metaInformation = $this->metaInformationFactory->loadInformation(EntityNestedProperty::class); + + $entity = new ValidTestEntity(); + + $solr = new SolrClientFake(); + $solr->mapper = $this->mapper; + $solr->response = array($entity); + $solr->metaFactory = $this->metaInformationFactory; + + $repo = new Repository($solr, $metaInformation); + + $found = $repo->findOneBy([ + 'collection.name' => '*test*test*' + ]); + + $this->assertEquals('{!parent which="id:entitynestedproperty_*"}name_t:*test*test*', $solr->query->getQuery()); + } } From 1d2cfa362a3aae54b3231ab2c5c98d0841a62e6a Mon Sep 17 00:00:00 2001 From: floriansemm Date: Mon, 12 Nov 2018 11:22:40 +0100 Subject: [PATCH 42/42] fix test --- Tests/Query/SolrQueryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Query/SolrQueryTest.php b/Tests/Query/SolrQueryTest.php index 908f9b56..e973c589 100644 --- a/Tests/Query/SolrQueryTest.php +++ b/Tests/Query/SolrQueryTest.php @@ -271,6 +271,6 @@ public function generateQueryForNestedDocuments() $query->addSearchTerm('collection.name', 'test*bar'); $query->addSearchTerm('title', 'test post'); - $this->assertEquals('title_s:"test post" OR {!parent which="id:post_*"}name_s:test', $query->getQuery()); + $this->assertEquals('title_s:"test post" OR {!parent which="id:post_*"}name_s:test*bar', $query->getQuery()); } }