diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a0332143 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Created by .ignore support plugin (hsz.mobi) +### Composer template +composer.phar +vendor/ +bin/ + +# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file +# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file +# composer.lock + + diff --git a/.travis.yml b/.travis.yml index c1c50560..f7e6ebd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,21 @@ language: php - -services: mongodb +sudo: false php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 + - 7.0 + - 7.1 + - 7.2 + - 7.3 + before_script: - - echo "extension=mongo.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` - - curl -s http://getcomposer.org/installer | php - - php composer.phar install --dev - - curl -sSL https://raw.githubusercontent.com/moliware/travis-solr/master/travis-solr.sh | SOLR_VERSION=4.8.0 bash + - curl -sSL https://raw.githubusercontent.com/floriansemm/travis-solr/master/travis-solr.sh | SOLR_CORE=core0 DEBUG=1 SOLR_VERSION=4.8.0 SOLR_CONFS="Tests/Resources/config/schema.xml" bash +install: + - composer self-update + - if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then yes '' | pecl -q install -f mongo; fi + - if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then pecl install -f mongodb; fi + - if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then composer config "platform.ext-mongo" "1.6.16" && composer require alcaeus/mongo-php-adapter; fi + - composer update script: - - phpunit -notifications: - email: - - fsemm.travis-ci@gmx.de + - ./bin/phpunit diff --git a/Client/Builder.php b/Client/Builder.php index afdd6eba..885546a2 100644 --- a/Client/Builder.php +++ b/Client/Builder.php @@ -1,6 +1,6 @@ logger = $logger; + } + + /** + * {@inheritdoc} + */ + protected function initPluginType() + { + $dispatcher = $this->client->getEventDispatcher(); + $dispatcher->addListener(Events::PRE_EXECUTE_REQUEST, [$this, 'preExecuteRequest']); + $dispatcher->addListener(Events::POST_EXECUTE_REQUEST, [$this, 'postExecuteRequest']); + } + + /** + * @param PreExecuteRequest $event + */ + public function preExecuteRequest(PreExecuteRequest $event) + { + $endpoint = $event->getEndpoint(); + $request = $event->getRequest(); + $uri = $request->getUri(); + + $path = sprintf('%s://%s:%s%s/%s', $endpoint->getScheme(), $endpoint->getHost(), $endpoint->getPort(), $endpoint->getPath(), urldecode($uri)); + + $requestInformation = [ + 'uri' => $path, + 'method' => $request->getMethod(), + 'raw_data' => $request->getRawData() + ]; + + $this->logger->startRequest($requestInformation); + } + + /** + * Issue stop logger + */ + public function postExecuteRequest() + { + $this->logger->stopRequest(); + } + + /** + * @return Client + */ + public function getClient() + { + return $this->client; + } +} \ No newline at end of file diff --git a/Client/Solarium/Plugin/RequestDebugger.php b/Client/Solarium/Plugin/RequestDebugger.php new file mode 100644 index 00000000..57fa14f3 --- /dev/null +++ b/Client/Solarium/Plugin/RequestDebugger.php @@ -0,0 +1,39 @@ +logger = $logger; + + parent::__construct(); + } + + /** + * @param PreExecuteRequest $event + */ + public function preExecuteRequest(PreExecuteRequest $event) + { + $this->logger->info(sprintf('run request: %s', urldecode($event->getRequest()->getUri()))); + } + +} \ No newline at end of file diff --git a/Client/Solarium/SolariumClientBuilder.php b/Client/Solarium/SolariumClientBuilder.php new file mode 100644 index 00000000..8c2d8ee8 --- /dev/null +++ b/Client/Solarium/SolariumClientBuilder.php @@ -0,0 +1,92 @@ +settings = $settings; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @param string $pluginName + * @param AbstractPlugin $plugin + */ + public function addPlugin($pluginName, AbstractPlugin $plugin) + { + $this->plugins[$pluginName] = $plugin; + } + + /** + * {@inheritdoc} + * + * @return Client + */ + public function build() + { + $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; + } +} diff --git a/Client/Client.php b/Client/Solarium/SolariumMulticoreClient.php similarity index 75% rename from Client/Client.php rename to Client/Solarium/SolariumMulticoreClient.php index 989a3e1c..71460d9d 100644 --- a/Client/Client.php +++ b/Client/Solarium/SolariumMulticoreClient.php @@ -1,26 +1,28 @@ solariumClient = $solariumClient; } @@ -44,8 +46,12 @@ public function update(DocumentInterface $doc, $index) */ public function delete(DocumentInterface $document, $index) { - $deleteQuery = new FindByIdentifierQuery(); + $documentFields = $document->getFields(); + $documentKey = $documentFields[MetaInformationInterface::DOCUMENT_KEY_FIELD_NAME]; + + $deleteQuery = new DeleteDocumentQuery(); $deleteQuery->setDocument($document); + $deleteQuery->setDocumentKey($documentKey); $delete = $this->solariumClient->createUpdate(); $delete->addDeleteQuery($deleteQuery->getQuery()); @@ -54,6 +60,9 @@ public function delete(DocumentInterface $document, $index) $this->applyQuery($delete, $index); } + /** + * Runs a *:* delete query on all cores + */ public function clearCores() { $delete = $this->solariumClient->createUpdate(); diff --git a/Client/SolrBuilder.php b/Client/SolrBuilder.php deleted file mode 100644 index 06656e94..00000000 --- a/Client/SolrBuilder.php +++ /dev/null @@ -1,32 +0,0 @@ -settings = $settings; - } - - /** - * @return Client - */ - public function build() - { - return new Client(array('endpoint' => $this->settings)); - } -} \ No newline at end of file diff --git a/Command/ClearIndexCommand.php b/Command/ClearIndexCommand.php index 3f126f43..61e8114d 100644 --- a/Command/ClearIndexCommand.php +++ b/Command/ClearIndexCommand.php @@ -1,15 +1,22 @@ setDescription('Clear the whole index'); } + /** + * {@inheritdoc} + */ protected function execute(InputInterface $input, OutputInterface $output) { $solr = $this->getContainer()->get('solr.client'); try { $solr->clearIndex(); - } catch (\Exception $e) { - } - - $results = $this->getContainer()->get('solr.console.command.results'); - if ($results->hasErrors()) { - $output->writeln('Clear index finished with errors!'); - } else { - $output->writeln('Index successful cleared successful'); + } catch (SolrException $e) { + $output->writeln(sprintf('A error occurs: %s', $e->getMessage())); } - if ($results->hasErrors()) { - $output->writeln(''); - $error = array_shift($results->getErrors()); - - $output->writeln(sprintf('Error: %s', $error->getMessage())); - } + $output->writeln('Index successful cleared.'); } } diff --git a/Command/ShowSchemaCommand.php b/Command/ShowSchemaCommand.php new file mode 100644 index 00000000..2516c28a --- /dev/null +++ b/Command/ShowSchemaCommand.php @@ -0,0 +1,141 @@ +setName('solr:schema:show') + ->setDescription('Show configured entities and their fields'); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $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; + } + } catch (SolrMappingException $e) { + continue; + } + + $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(): '-')); + + $simpleFields = $this->getSimpleFields($metaInformation); + + $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))); + + 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); + } + } + + } + + } + + /** + * @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 diff --git a/Command/SynchronizeIndexCommand.php b/Command/SynchronizeIndexCommand.php index a41b7374..0dfdbec4 100644 --- a/Command/SynchronizeIndexCommand.php +++ b/Command/SynchronizeIndexCommand.php @@ -1,99 +1,206 @@ setName('solr:synchronize') - ->addArgument('entity', InputArgument::REQUIRED, 'The entity you want to index') - ->addOption( - 'source', - null, - InputArgument::OPTIONAL, - 'specify a source from where to load entities [relational, mongodb]', - 'relational' - ) + $this->setName('solr:index:populate') + ->addArgument('entity', InputArgument::OPTIONAL, 'The entity you want to index', null) + ->addOption('flushsize', null, InputOption::VALUE_OPTIONAL, 'Number of items to handle before flushing data', 500) + ->addOption('source', null, InputArgument::OPTIONAL, 'specify a source from where to load entities [relational, mongodb]', null) + ->addOption('start-offset', null, InputOption::VALUE_OPTIONAL, 'Start with row', 0) ->setDescription('Index all entities'); } + /** + * {@inheritdoc} + */ protected function execute(InputInterface $input, OutputInterface $output) { - $entity = $input->getArgument('entity'); + $entities = $this->getIndexableEntities($input->getArgument('entity')); $source = $input->getOption('source'); + if ($source !== null) { + $output->writeln('The source option is deprecated and will be removed in version 2.0'); + } - $objectManager = $this->getObjectManager($source); + $startOffset = $input->getOption('start-offset'); + $batchSize = $input->getOption('flushsize'); + $solr = $this->getContainer()->get('solr.client'); - try { - $repository = $objectManager->getRepository($entity); - } catch (\Exception $e) { - $output->writeln(sprintf('No repository found for "%s", check your input', $entity)); + if ($startOffset > 0 && count($entities) > 1) { + $output->writeln('Wrong usage. Please use start-offset option together with the entity argument.'); return; } - $entities = $repository->findAll(); + foreach ($entities as $entityClassname) { + $objectManager = $this->getObjectManager($entityClassname); - if (count($entities) == 0) { - $output->writeln('No entities found for indexing'); + $output->writeln(sprintf('Indexing: %s', $entityClassname)); - return; + try { + $repository = $objectManager->getRepository($entityClassname); + } catch (\Exception $e) { + $output->writeln(sprintf('No repository found for "%s", check your input', $entityClassname)); + + continue; + } + + $totalSize = $this->getTotalNumberOfEntities($entityClassname, $startOffset); + + if ($totalSize >= 500000) { + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Indexing more than 500000 entities does not perform well and can exhaust the whole memory. Execute anyway?', false); + + if (!$helper->ask($input, $output, $question)) { + $output->writeln(''); + + continue; + } + } + + if ($totalSize === 0) { + $output->writeln('No entities found for indexing'); + + continue; + } + + $output->writeln(sprintf('Synchronize %s entities', $totalSize)); + + $batchLoops = ceil($totalSize / $batchSize); + + for ($i = 0; $i <= $batchLoops; $i++) { + $offset = $i * $batchSize; + if ($startOffset && $i == 0) { + $offset = $startOffset; + $i++; + } + + $entities = $repository->findBy([], null, $batchSize, $offset); + + try { + $solr->synchronizeIndex($entities); + } catch (\Exception $e) { + $output->writeln(sprintf('A error occurs: %s', $e->getMessage())); + } + } + + $output->writeln('Synchronization finished'); + $output->writeln(''); } + } - $solr = $this->getContainer()->get('solr.client'); + /** + * @param string $entityClassname + * + * @throws \RuntimeException if no doctrine instance is configured + * + * @return ObjectManager + */ + private function getObjectManager($entityClassname) + { + $objectManager = $this->getContainer()->get('doctrine')->getManagerForClass($entityClassname); + if ($objectManager) { + return $objectManager; + } - foreach ($entities as $entity) { - try { - $solr->synchronizeIndex($entity); - } catch (\Exception $e) {} + $objectManager = $this->getContainer()->get('doctrine_mongodb')->getManagerForClass($entityClassname); + if ($objectManager) { + return $objectManager; } - $results = $this->getContainer()->get('solr.console.command.results'); - if ($results->hasErrors()) { - $output->writeln('Synchronization finished with errors!'); - } else { - $output->writeln('Synchronization successful'); + throw new \RuntimeException(sprintf('Class "%s" is not a managed entity', $entityClassname)); + } + + /** + * Get a list of entities which are indexable by Solr + * + * @param null|string $entity + * + * @return array + */ + private function getIndexableEntities($entity = null) + { + if ($entity) { + return [$entity]; } - $output->writeln(''); - $output->writeln(sprintf('Synchronized Documents: %s', $results->getSucceed())); - $output->writeln(sprintf('Not Synchronized Documents: %s', $results->getErrored())); - $output->writeln(''); + $entities = []; + $namespaces = $this->getContainer()->get('solr.doctrine.classnameresolver.known_entity_namespaces'); + $metaInformationFactory = $this->getContainer()->get('solr.meta.information.factory'); - $output->writeln(sprintf('Overall: %s', $results->getOverall())); - if ($results->hasErrors()) { - $errorList = new ConsoleErrorListOutput($output, $this->getHelper('table'), $results->getErrors()); - $errorList->render(); + foreach ($namespaces->getEntityClassnames() as $classname) { + try { + $metaInformation = $metaInformationFactory->loadInformation($classname); + if ($metaInformation->isNested()) { + continue; + } + + array_push($entities, $metaInformation->getClassName()); + } catch (SolrMappingException $e) { + continue; + } } + + return $entities; } /** - * @param string $source - * @throws \InvalidArgumentException if $source is unknown - * @throws \RuntimeException if no doctrine instance is configured - * @return AbstractManagerRegistry + * Get the total number of entities in a repository + * + * @param string $entity + * @param int $startOffset + * + * @return int + * + * @throws \Exception if no primary key was found for the given entity */ - private function getObjectManager($source) + private function getTotalNumberOfEntities($entity, $startOffset) { - $objectManager = null; + $objectManager = $this->getObjectManager($entity); + $repository = $objectManager->getRepository($entity); - if ($source == 'relational') { - $objectManager = $this->getContainer()->get('doctrine'); + if ($repository instanceof DocumentRepository) { + $totalSize = $repository->createQueryBuilder() + ->getQuery() + ->count(); } else { - if ($source == 'mongodb') { - $objectManager = $this->getContainer()->get('doctrine_mongodb'); - } else { - throw new \InvalidArgumentException(sprintf('Unknown source %s', $source)); + $dataStoreMetadata = $objectManager->getClassMetadata($entity); + + $identifierFieldNames = $dataStoreMetadata->getIdentifierFieldNames(); + + if (!count($identifierFieldNames)) { + throw new \Exception(sprintf('No primary key found for entity %s', $entity)); } + + $countableColumn = reset($identifierFieldNames); + + /** @var EntityRepository $repository */ + $totalSize = $repository->createQueryBuilder('size') + ->select(sprintf('count(size.%s)', $countableColumn)) + ->getQuery() + ->getSingleScalarResult(); } - return $objectManager; + return $totalSize - $startOffset; } } diff --git a/Console/CommandResult.php b/Console/CommandResult.php deleted file mode 100644 index 0520ade9..00000000 --- a/Console/CommandResult.php +++ /dev/null @@ -1,58 +0,0 @@ -resultId = $resultId; - $this->entity = $entity; - $this->message = $message; - } - - /** - * @return int - */ - public function getResultId() - { - return $this->resultId; - } - - /** - * @return string - */ - public function getEntity() - { - return $this->entity; - } - - /** - * @return string - */ - public function getMessage() - { - return $this->message; - } -} \ No newline at end of file diff --git a/Console/ConsoleCommandResults.php b/Console/ConsoleCommandResults.php deleted file mode 100644 index 9d75f3a0..00000000 --- a/Console/ConsoleCommandResults.php +++ /dev/null @@ -1,92 +0,0 @@ -success[$result->getResultId()] = $result; - } - - /** - * @param CommandResult $result - */ - public function error(CommandResult $result) - { - $this->errors[$result->getResultId()] = $result; - } - - /** - * @return CommandResult[] - */ - public function getErrors() - { - return $this->errors; - } - - /** - * @return CommandResult[] - */ - public function getSuccess() - { - return $this->success; - } - - /** - * @return bool - */ - public function hasErrors() - { - return count($this->errors) > 0; - } - - /** - * @return int - */ - public function getOverall() - { - return $this->getErrored() + $this->getSucceed(); - } - - /** - * filtering of succeed result required: - * - * error-event will trigger after exception. the normal program-flow continues WITH post_update/insert events - * - * @return int - */ - public function getSucceed() - { - foreach ($this->success as $resultId => $result) { - if (isset($this->errors[$resultId])) { - unset($this->success[$resultId]); - } - } - - return count($this->success); - } - - /** - * @return int - */ - public function getErrored() - { - return count($this->errors); - } -} \ No newline at end of file diff --git a/Console/ConsoleErrorListOutput.php b/Console/ConsoleErrorListOutput.php deleted file mode 100644 index 79d9c621..00000000 --- a/Console/ConsoleErrorListOutput.php +++ /dev/null @@ -1,52 +0,0 @@ -output = $output; - $this->errors = $errors; - $this->tableHelperSet = $tableHelperSet; - } - - public function render() - { - $this->output->writeln(''); - $this->output->writeln('Errors:'); - $rows = array(); - foreach ($this->errors as $error) { - $rows[] = array($error->getEntity(), $error->getResultId(), $error->getMessage()); - } - - $this->tableHelperSet->setHeaders(array('Entity', 'ID', 'Error')) - ->setRows($rows); - - $this->tableHelperSet->render($this->output); - } -} \ No newline at end of file diff --git a/Console/ConsoleResultFactory.php b/Console/ConsoleResultFactory.php deleted file mode 100644 index ec16955f..00000000 --- a/Console/ConsoleResultFactory.php +++ /dev/null @@ -1,64 +0,0 @@ -getResultId($event), - $this->getClassname($event), - $this->getMessage($event) - ); - - } - - /** - * @param Event $event - * @return null|number - */ - private function getResultId(Event $event) - { - if ($event->getMetaInformation() == null) { - return null; - } - - return $event->getMetaInformation()->getEntityId(); - } - - /** - * @param Event $event - * @return string - */ - private function getClassname(Event $event) - { - if ($event->getMetaInformation() == null) { - return ''; - } - - return $event->getMetaInformation()->getClassName(); - } - - /** - * @param Event $event - * @return string - */ - private function getMessage(Event $event) - { - if (!$event instanceof ErrorEvent) { - return ''; - } - - return $event->getExceptionMessage(); - } -} \ No newline at end of file diff --git a/DataCollector/RequestCollector.php b/DataCollector/RequestCollector.php new file mode 100644 index 00000000..b5649ae9 --- /dev/null +++ b/DataCollector/RequestCollector.php @@ -0,0 +1,117 @@ +logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = [ + 'queries' => array_map(function ($query) { + return $this->parseQuery($query); + }, $this->logger->getQueries()) + ]; + } + + /** + * @return int + */ + public function getQueryCount() + { + return count($this->data['queries']); + } + + /** + * @return array + */ + public function getQueries() + { + return $this->data['queries']; + } + + /** + * @return int + */ + public function getTime() + { + $time = 0; + foreach ($this->data['queries'] as $query) { + $time += $query['executionMS']; + } + + return $time; + } + + /** + * @param array $request + * + * @return array + */ + public function parseQuery($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(); + + parse_str($params, $stub); + $request['stub'] = Kernel::VERSION_ID >= 30200 ? $varCloner->cloneVar($stub) : $stub; + } + + return $request; + } + + /** + * @param DebugLogger $logger + */ + public function setLogger(DebugLogger $logger) + { + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'solr'; + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->data = [ + 'queries' => [], + ]; + } +} diff --git a/DependencyInjection/Compiler/AddCreateDocumentCommandPass.php b/DependencyInjection/Compiler/AddCreateDocumentCommandPass.php deleted file mode 100644 index 89ce7e94..00000000 --- a/DependencyInjection/Compiler/AddCreateDocumentCommandPass.php +++ /dev/null @@ -1,29 +0,0 @@ -findTaggedServiceIds('solr.document.command'); - - $factory = $container->getDefinition('solr.mapping.factory'); - - foreach ($definitions as $service => $definition) { - $factory->addMethodCall( - 'add', - array( - new Reference($service), - $definition[0]['command'] - ) - ); - } - } -} diff --git a/DependencyInjection/Compiler/AddSolariumPluginsPass.php b/DependencyInjection/Compiler/AddSolariumPluginsPass.php new file mode 100644 index 00000000..c605c830 --- /dev/null +++ b/DependencyInjection/Compiler/AddSolariumPluginsPass.php @@ -0,0 +1,32 @@ +findTaggedServiceIds('solarium.client.plugin'); + + $clientBuilder = $container->getDefinition('solr.client.adapter.builder'); + foreach ($plugins as $service => $definition) { + $clientBuilder->addMethodCall( + 'addPlugin', + array( + $definition[0]['plugin-name'], + new Reference($service) + ) + ); + } + } +} \ No newline at end of file diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index dbf8954e..955b051a 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -9,7 +9,7 @@ class Configuration implements ConfigurationInterface { /** - * @return TreeBuilder + * {@inheritdoc} */ public function getConfigTreeBuilder() { @@ -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() @@ -29,16 +31,6 @@ public function getConfigTreeBuilder() ->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(); diff --git a/DependencyInjection/FSSolrExtension.php b/DependencyInjection/FSSolrExtension.php index aa2e86cb..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; @@ -14,8 +12,7 @@ class FSSolrExtension extends Extension { /** - * @param array $configs - * @param ContainerBuilder $container + * {@inheritdoc} */ public function load(array $configs, ContainerBuilder $container) { @@ -29,7 +26,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); @@ -37,7 +36,7 @@ public function load(array $configs, ContainerBuilder $container) } /** - * @param array $config + * @param array $config * @param ContainerBuilder $container */ private function setupClients(array $config, ContainerBuilder $container) @@ -46,11 +45,12 @@ private function setupClients(array $config, ContainerBuilder $container) $builderDefinition = $container->getDefinition('solr.client.adapter.builder'); $builderDefinition->replaceArgument(0, $endpoints); + $builderDefinition->addMethodCall('addPlugin', array('request_debugger', new Reference('solr.debug.client_debugger'))); } /** * - * @param array $config + * @param array $config * @param ContainerBuilder $container */ private function setupDoctrineConfiguration(array $config, ContainerBuilder $container) @@ -59,7 +59,7 @@ private function setupDoctrineConfiguration(array $config, ContainerBuilder $con $entityManagers = $container->getParameter('doctrine.entity_managers'); $entityManagersNames = array_keys($entityManagers); - foreach($entityManagersNames as $entityManager) { + foreach ($entityManagersNames as $entityManager) { $container->getDefinition('solr.doctrine.classnameresolver.known_entity_namespaces')->addMethodCall( 'addEntityNamespaces', array(new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager))) @@ -71,7 +71,7 @@ private function setupDoctrineConfiguration(array $config, ContainerBuilder $con $documentManagers = $container->getParameter('doctrine_mongodb.odm.document_managers'); $documentManagersNames = array_keys($documentManagers); - foreach($documentManagersNames as $documentManager) { + foreach ($documentManagersNames as $documentManager) { $container->getDefinition('solr.doctrine.classnameresolver.known_entity_namespaces')->addMethodCall( 'addDocumentNamespaces', array(new Reference(sprintf('doctrine_mongodb.odm.%s_configuration', $documentManager))) @@ -90,7 +90,7 @@ private function setupDoctrineConfiguration(array $config, ContainerBuilder $con * * listener-methods expecting different types of events * - * @param array $config + * @param array $config * @param ContainerBuilder $container */ private function setupDoctrineListener(array $config, ContainerBuilder $container) @@ -102,39 +102,17 @@ private function setupDoctrineListener(array $config, ContainerBuilder $containe } if ($this->isODMConfigured($container)) { - $container->getDefinition('solr.delete.document.odm.listener')->addTag( - 'doctrine_mongodb.odm.event_listener', - array('event' => 'preRemove') - ); - $container->getDefinition('solr.update.document.odm.listener')->addTag( - 'doctrine_mongodb.odm.event_listener', - array('event' => 'postUpdate') - ); - $container->getDefinition('solr.add.document.odm.listener')->addTag( - 'doctrine_mongodb.odm.event_listener', - array('event' => 'postPersist') - ); - + $container->getDefinition('solr.document.odm.subscriber')->addTag('doctrine_mongodb.odm.event_subscriber'); } if ($this->isOrmConfigured($container)) { - $container->getDefinition('solr.add.document.orm.listener')->addTag( - 'doctrine.event_listener', - array('event' => 'postPersist') - ); - $container->getDefinition('solr.delete.document.orm.listener')->addTag( - 'doctrine.event_listener', - array('event' => 'preRemove') - ); - $container->getDefinition('solr.update.document.orm.listener')->addTag( - 'doctrine.event_listener', - array('event' => 'postUpdate') - ); + $container->getDefinition('solr.document.orm.subscriber')->addTag('doctrine.event_subscriber'); } } /** * @param ContainerBuilder $container + * * @return boolean */ private function isODMConfigured(ContainerBuilder $container) @@ -144,6 +122,7 @@ private function isODMConfigured(ContainerBuilder $container) /** * @param ContainerBuilder $container + * * @return boolean */ private function isOrmConfigured(ContainerBuilder $container) diff --git a/Doctrine/AbstractIndexingListener.php b/Doctrine/AbstractIndexingListener.php new file mode 100644 index 00000000..1db9e405 --- /dev/null +++ b/Doctrine/AbstractIndexingListener.php @@ -0,0 +1,93 @@ +solr = $solr; + $this->metaInformationFactory = $metaInformationFactory; + $this->logger = $logger; + } + + /** + * @param array $doctrineChangeSet + * @param object $entity + * + * @return bool + */ + protected function hasChanged($doctrineChangeSet, $entity) + { + if (empty($doctrineChangeSet)) { + return false; + } + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $documentChangeSet = array(); + + /* Check all Solr fields on this entity and check if this field is in the change set */ + foreach ($metaInformation->getFields() as $field) { + if (array_key_exists($field->name, $doctrineChangeSet)) { + $documentChangeSet[] = $field->name; + } + } + + return count($documentChangeSet) > 0; + } + + /** + * @param object $entity + * + * @return bool + */ + protected function isNested($entity) + { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + return $metaInformation->isNested(); + } + + /** + * @param object $entity + * + * @return bool + */ + protected function isAbleToIndex($entity) + { + try { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + } catch (SolrMappingException $e) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/Doctrine/Annotation/AnnotationReader.php b/Doctrine/Annotation/AnnotationReader.php index e56d90ac..d8c8ed64 100644 --- a/Doctrine/Annotation/AnnotationReader.php +++ b/Doctrine/Annotation/AnnotationReader.php @@ -1,41 +1,50 @@ reader = new Reader(); + $this->reader = $reader; } /** * reads the entity and returns a set of annotations * - * @param string $entity + * @param object $entity * @param string $type * - * @return array + * @return Annotation[] */ private function getPropertiesByType($entity, $type) { - $reflectionClass = new \ReflectionClass($entity); - $properties = array_merge($reflectionClass->getProperties(), $this->getParentProperties($reflectionClass)); + $properties = $this->readClassProperties($entity); - $fields = array(); + $fields = []; foreach ($properties as $property) { $annotation = $this->reader->getPropertyAnnotation($property, $type); @@ -61,11 +70,11 @@ private function getPropertiesByType($entity, $type) private function getParentProperties(\ReflectionClass $reflectionClass) { $parent = $reflectionClass->getParentClass(); - if ($parent == null) { - return array(); + if ($parent != null) { + return array_merge($reflectionClass->getProperties(), $this->getParentProperties($parent)); } - return $parent->getProperties(); + return $reflectionClass->getProperties(); } /** @@ -78,40 +87,75 @@ 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 * * @return number * - * @throws \InvalidArgumentException if the boost value is not numeric + * @throws AnnotationReaderException if the boost value is not numeric */ 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; } - try { - $boostValue = $annotation->getBoost(); - } catch (\InvalidArgumentException $e) { - throw new \InvalidArgumentException(sprintf($e->getMessage() . ' for entity %s', get_class($entity))); + $boostValue = $annotation->getBoost(); + if (!is_numeric($boostValue)) { + throw new AnnotationReaderException(sprintf('Invalid boost value "%s" in class "%s" configured', $boostValue, get_class($entity))); + } + + if ($boostValue === 0) { + return null; } return $boostValue; } /** - * @param $entity + * @param object $entity * * @return string */ public function getDocumentIndex($entity) { - $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_INDEX_CLASS); + $annotation = $this->getClassAnnotation($entity, self::DOCUMENT_CLASS); if (!$annotation instanceof Document) { - return ''; + return null; } $indexHandler = $annotation->indexHandler; @@ -125,16 +169,16 @@ public function getDocumentIndex($entity) /** * @param object $entity * - * @return Type + * @return Id * - * @throws \RuntimeException + * @throws AnnotationReaderException if given $entity has no identifier */ public function getIdentifier($entity) { $id = $this->getPropertiesByType($entity, self::FIELD_IDENTIFIER_CLASS); if (count($id) == 0) { - throw new \RuntimeException('no identifer declared in entity ' . get_class($entity)); + throw new AnnotationReaderException('no identifer declared in entity ' . get_class($entity)); } return reset($id); @@ -167,11 +211,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); @@ -187,9 +229,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; } /** @@ -208,11 +256,57 @@ public function getSynchronizationCallback($entity) return $annotation->callback; } + /** + * @param object $entity + * + * @return bool + */ + public function isOrm($entity) + { + $annotation = $this->getClassAnnotation($entity, 'Doctrine\ORM\Mapping\Entity'); + + if ($annotation === null) { + return false; + } + + return true; + } + + /** + * @param object $entity + * + * @return bool + */ + public function isOdm($entity) + { + $annotation = $this->getClassAnnotation($entity, 'Doctrine\ODM\MongoDB\Mapping\Annotations\Document'); + + if ($annotation === null) { + return false; + } + + 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 * - * @return string + * @return Annotation|null */ private function getClassAnnotation($entity, $annotationName) { @@ -226,4 +320,29 @@ private function getClassAnnotation($entity, $annotationName) return $annotation; } + + /** + * @param object $entity + * + * @return \ReflectionProperty[] + */ + private function readClassProperties($entity) + { + $className = get_class($entity); + if (isset($this->entityProperties[$className])) { + return $this->entityProperties[$className]; + } + + $reflectionClass = new \ReflectionClass($entity); + $inheritedProperties = array_merge($this->getParentProperties($reflectionClass), $reflectionClass->getProperties()); + + $properties = []; + foreach ($inheritedProperties as $property) { + $properties[$property->getName()] = $property; + } + + $this->entityProperties[$className] = $properties; + + return $properties; + } } diff --git a/Doctrine/Annotation/AnnotationReaderException.php b/Doctrine/Annotation/AnnotationReaderException.php new file mode 100644 index 00000000..d48a4f2f --- /dev/null +++ b/Doctrine/Annotation/AnnotationReaderException.php @@ -0,0 +1,8 @@ +boost)) { - throw new \InvalidArgumentException(sprintf('Invalid boost value %s', $this->boost)); - } - - $float = floatval($this->boost); - return $float ?: null; + return $this->boost; } /** diff --git a/Doctrine/Annotation/Field.php b/Doctrine/Annotation/Field.php index 04baf7af..3f87f08b 100644 --- a/Doctrine/Annotation/Field.php +++ b/Doctrine/Annotation/Field.php @@ -2,8 +2,11 @@ namespace FS\SolrBundle\Doctrine\Annotation; use Doctrine\Common\Annotations\Annotation; +use phpDocumentor\Reflection\DocBlock\Type\Collection; /** + * Defines a field of a solr-document + * * @Annotation */ class Field extends Annotation @@ -20,14 +23,34 @@ class Field extends Annotation public $name; /** - * @var numeric + * @var float */ public $boost = 0; + /** + * @var string + */ + public $getter; + + /** + * @var string + */ + public $fieldModifier; + + /** + * @var string + */ + public $nestedClass; + + /** + * @var array + */ + private static $TYP_MAPPING = array(); + /** * @var array */ - private static $TYP_MAPPING = array( + private static $TYP_SIMPLE_MAPPING = array( 'string' => '_s', 'text' => '_t', 'date' => '_dt', @@ -36,6 +59,22 @@ class Field extends Annotation 'long' => '_l', 'float' => '_f', 'double' => '_d', + 'datetime' => '_dt', + 'point' => '_p' + ); + + /** + * @var array + */ + private static $TYP_COMPLEX_MAPPING = array( + 'doubles' => '_ds', + 'floats' => '_fs', + 'longs' => '_ls', + 'integers' => '_is', + 'booleans' => '_bs', + 'dates' => '_dts', + 'texts' => '_txt', + 'strings' => '_ss', ); /** @@ -44,6 +83,7 @@ class Field extends Annotation * eg: title_s * * @throws \RuntimeException + * * @return string */ public function getNameWithAlias() @@ -53,10 +93,13 @@ public function getNameWithAlias() /** * @param string $type + * * @return string */ private function getTypeSuffix($type) { + self::$TYP_MAPPING = array_merge(self::$TYP_COMPLEX_MAPPING, self::$TYP_SIMPLE_MAPPING); + if ($type == '') { return ''; } @@ -68,6 +111,24 @@ private function getTypeSuffix($type) return self::$TYP_MAPPING[$this->type]; } + /** + * Related object getter name + * + * @return string + */ + public function getGetterName() + { + return $this->getter; + } + + /** + * @return string + */ + public function getFieldModifier() + { + return $this->fieldModifier; + } + /** * @return string */ @@ -86,6 +147,7 @@ public function __toString() /** * @throws \InvalidArgumentException if boost is not a number + * * @return number */ public function getBoost() @@ -123,4 +185,12 @@ function ($value) { return implode('_', $words); } + + /** + * @return array + */ + public static function getComplexFieldMapping() + { + return self::$TYP_COMPLEX_MAPPING; + } } diff --git a/Doctrine/Annotation/Id.php b/Doctrine/Annotation/Id.php index bb4dafcd..1d8b207f 100644 --- a/Doctrine/Annotation/Id.php +++ b/Doctrine/Annotation/Id.php @@ -1,4 +1,5 @@ knownNamespaceAlias = array_merge($this->knownNamespaceAlias, $configuration->getDocumentNamespaces()); + + if ($configuration->getMetadataDriverImpl()) { + $this->entityClassnames = array_merge($this->entityClassnames, $configuration->getMetadataDriverImpl()->getAllClassNames()); + } } /** @@ -26,6 +38,10 @@ public function addDocumentNamespaces(OdmConfiguration $configuration) public function addEntityNamespaces(OrmConfiguration $configuration) { $this->knownNamespaceAlias = array_merge($this->knownNamespaceAlias, $configuration->getEntityNamespaces()); + + if ($configuration->getMetadataDriverImpl()) { + $this->entityClassnames = array_merge($this->entityClassnames, $configuration->getMetadataDriverImpl()->getAllClassNames()); + } } /** @@ -59,4 +75,12 @@ public function getAllNamespaceAliases() { return array_keys($this->knownNamespaceAlias); } -} \ No newline at end of file + + /** + * @return array + */ + public function getEntityClassnames() + { + return $this->entityClassnames; + } +} \ No newline at end of file diff --git a/Doctrine/Hydration/DoctrineHydrator.php b/Doctrine/Hydration/DoctrineHydrator.php index aa195723..88c0f4c3 100644 --- a/Doctrine/Hydration/DoctrineHydrator.php +++ b/Doctrine/Hydration/DoctrineHydrator.php @@ -2,45 +2,76 @@ namespace FS\SolrBundle\Doctrine\Hydration; -use FS\SolrBundle\Doctrine\Mapper\MetaInformation; +use Doctrine\Common\Persistence\ManagerRegistry; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; use Symfony\Bridge\Doctrine\RegistryInterface; -class DoctrineHydrator implements Hydrator +/** + * A doctrine-hydrator finds the entity for a given solr-document. This entity is updated with the solr-document values. + * + * The hydration is necessary because fields, which are not declared as solr-field, will not populate in the result. + */ +class DoctrineHydrator implements HydratorInterface { /** - * @var RegistryInterface + * @var ManagerRegistry */ - private $doctrine; + private $ormManager; /** - * @var Hydrator + * @var ManagerRegistry + */ + private $odmManager; + + /** + * @var HydratorInterface */ private $valueHydrator; /** - * @param RegistryInterface $doctrine - * @param Hydrator $valueHydrator + * @param HydratorInterface $valueHydrator */ - public function __construct(RegistryInterface $doctrine, Hydrator $valueHydrator) + public function __construct(HydratorInterface $valueHydrator) { - $this->doctrine = $doctrine; $this->valueHydrator = $valueHydrator; } /** - * @param $document - * @param MetaInformation $metaInformation - * - * @return object + * @param ManagerRegistry $ormManager + */ + public function setOrmManager($ormManager) + { + $this->ormManager = $ormManager; + } + + /** + * @param ManagerRegistry $odmManager */ - public function hydrate($document, MetaInformation $metaInformation) + public function setOdmManager($odmManager) { - $entityId = $document->id; - $doctrineEntity = $this->doctrine - ->getManager() - ->getRepository($metaInformation->getClassName()) - ->find($entityId); + $this->odmManager = $odmManager; + } + + /** + * {@inheritdoc} + */ + public function hydrate($document, MetaInformationInterface $metaInformation) + { + $entityId = $this->valueHydrator->removePrefixedKeyValues($document['id']); + + $doctrineEntity = null; + if ($metaInformation->getDoctrineMapperType() == MetaInformationInterface::DOCTRINE_MAPPER_TYPE_RELATIONAL) { + $doctrineEntity = $this->ormManager + ->getManager() + ->getRepository($metaInformation->getClassName()) + ->find($entityId); + } elseif ($metaInformation->getDoctrineMapperType() == MetaInformationInterface::DOCTRINE_MAPPER_TYPE_DOCUMENT) { + $doctrineEntity = $this->odmManager + ->getManager() + ->getRepository($metaInformation->getClassName()) + ->find($entityId); + } if ($doctrineEntity !== null) { $metaInformation->setEntity($doctrineEntity); diff --git a/Doctrine/Hydration/DoctrineHydratorFactory.php b/Doctrine/Hydration/DoctrineHydratorFactory.php new file mode 100644 index 00000000..900e791b --- /dev/null +++ b/Doctrine/Hydration/DoctrineHydratorFactory.php @@ -0,0 +1,40 @@ +container = $container; + } + + /** + * @return DoctrineHydrator + */ + public function factory() + { + $valueHydrator = $this->container->get('solr.doctrine.hydration.doctrine_value_hydrator'); + + $hydrator = new DoctrineHydrator($valueHydrator); + if ($this->container->has('doctrine')) { + $hydrator->setOrmManager($this->container->get('doctrine')); + } + + if ($this->container->has('doctrine_mongodb')) { + $hydrator->setOdmManager($this->container->get('doctrine_mongodb')); + } + + return $hydrator; + } +} \ No newline at end of file diff --git a/Doctrine/Hydration/DoctrineValueHydrator.php b/Doctrine/Hydration/DoctrineValueHydrator.php new file mode 100644 index 00000000..d39efda9 --- /dev/null +++ b/Doctrine/Hydration/DoctrineValueHydrator.php @@ -0,0 +1,27 @@ +getField($fieldName) && $metaInformation->getField($fieldName)->getter) { + return false; + } + + return true; + } + +} \ No newline at end of file diff --git a/Doctrine/Hydration/HydrationModes.php b/Doctrine/Hydration/HydrationModes.php index 553bbc43..7ff89307 100644 --- a/Doctrine/Hydration/HydrationModes.php +++ b/Doctrine/Hydration/HydrationModes.php @@ -2,9 +2,15 @@ namespace FS\SolrBundle\Doctrine\Hydration; - class HydrationModes { + /** + * use only the values from the index. Ignore not indexed db values. + */ const HYDRATE_INDEX = 'index'; + + /** + * use values from the index and db. Resulting entity holds also not indexed values. + */ const HYDRATE_DOCTRINE = 'doctrine'; } \ No newline at end of file diff --git a/Doctrine/Hydration/Hydrator.php b/Doctrine/Hydration/Hydrator.php deleted file mode 100644 index 6a559914..00000000 --- a/Doctrine/Hydration/Hydrator.php +++ /dev/null @@ -1,16 +0,0 @@ -valueHydrator = $valueHydrator; } /** - * @param $document - * @param MetaInformation $metaInformation - * - * @return object + * {@inheritdoc} */ - public function hydrate($document, MetaInformation $metaInformation) + public function hydrate($document, MetaInformationInterface $metaInformation) { $sourceTargetEntity = $metaInformation->getEntity(); $targetEntity = clone $sourceTargetEntity; diff --git a/Doctrine/Hydration/NoDatabaseValueHydrator.php b/Doctrine/Hydration/NoDatabaseValueHydrator.php new file mode 100644 index 00000000..ba8cf42a --- /dev/null +++ b/Doctrine/Hydration/NoDatabaseValueHydrator.php @@ -0,0 +1,20 @@ +setterName = $setterName; + } + + /** + * {@inheritdoc} + */ + public function setValue($targetObject, $value) + { + $targetObject->{$this->setterName}($value); + } +} \ No newline at end of file diff --git a/Doctrine/Hydration/PropertyAccessor/PrivatePropertyAccessor.php b/Doctrine/Hydration/PropertyAccessor/PrivatePropertyAccessor.php new file mode 100644 index 00000000..07019f88 --- /dev/null +++ b/Doctrine/Hydration/PropertyAccessor/PrivatePropertyAccessor.php @@ -0,0 +1,29 @@ +classProperty = $classProperty; + } + + /** + * {@inheritdoc} + */ + public function setValue($targetObject, $value) + { + $this->classProperty->setAccessible(true); + $this->classProperty->setValue($targetObject, $value); + } +} \ No newline at end of file diff --git a/Doctrine/Hydration/PropertyAccessor/PropertyAccessorInterface.php b/Doctrine/Hydration/PropertyAccessor/PropertyAccessorInterface.php new file mode 100644 index 00000000..e4a5e001 --- /dev/null +++ b/Doctrine/Hydration/PropertyAccessor/PropertyAccessorInterface.php @@ -0,0 +1,15 @@ +cache[$metaInformation->getDocumentName()])) { + $this->cache[$metaInformation->getDocumentName()] = array(); + } + $targetEntity = $metaInformation->getEntity(); $reflectionClass = new \ReflectionClass($targetEntity); foreach ($document as $property => $value) { - try { + if ($property === MetaInformationInterface::DOCUMENT_KEY_FIELD_NAME) { + $value = $this->removePrefixedKeyValues($value); + } + + // skip field if value is array or "flat" object + // hydrated object should contain a list of real entities / entity + if ($this->mapValue($property, $value, $metaInformation) == false) { + continue; + } + + if (isset($this->cache[$metaInformation->getDocumentName()][$property])) { + $this->cache[$metaInformation->getDocumentName()][$property]->setValue($targetEntity, $value); + + continue; + } + + // find setter method + $camelCasePropertyName = $this->toCamelCase($this->removeFieldSuffix($property)); + $setterMethodName = 'set'.ucfirst($camelCasePropertyName); + if (method_exists($targetEntity, $setterMethodName)) { + $accessor = new MethodCallPropertyAccessor($setterMethodName); + $accessor->setValue($targetEntity, $value); + + $this->cache[$metaInformation->getDocumentName()][$property] = $accessor; + + continue; + } + + + if ($reflectionClass->hasProperty($this->removeFieldSuffix($property))) { $classProperty = $reflectionClass->getProperty($this->removeFieldSuffix($property)); - } catch (\ReflectionException $e) { - try { - $classProperty = $reflectionClass->getProperty( - $this->toCamelCase($this->removeFieldSuffix($property)) - ); - } catch (\ReflectionException $e) { + } else { + // could no found document-field in underscore notation, transform them to camel-case notation + $camelCasePropertyName = $this->toCamelCase($this->removeFieldSuffix($property)); + if ($reflectionClass->hasProperty($camelCasePropertyName) == false) { continue; } + + $classProperty = $reflectionClass->getProperty($camelCasePropertyName); } - $classProperty->setAccessible(true); - $classProperty->setValue($targetEntity, $value); + $accessor = new PrivatePropertyAccessor($classProperty); + $accessor->setValue($targetEntity, $value); + + $this->cache[$metaInformation->getDocumentName()][$property] = $accessor; } return $targetEntity; @@ -46,7 +91,7 @@ public function hydrate($document, MetaInformation $metaInformation) * * @return string */ - private function removeFieldSuffix($property) + protected function removeFieldSuffix($property) { if (($pos = strrpos($property, '_')) !== false) { return substr($property, 0, $pos); @@ -55,6 +100,22 @@ private function removeFieldSuffix($property) return $property; } + /** + * keyfield product_1 becomes 1 + * + * @param string $value + * + * @return string + */ + public function removePrefixedKeyValues($value) + { + if (($pos = strrpos($value, '_')) !== false) { + return substr($value, ($pos + 1)); + } + + return $value; + } + /** * returns field name camelcased if it has underlines * @@ -72,4 +133,18 @@ private function toCamelCase($fieldname) return lcfirst($pascalCased); } + + /** + * Check if given field and value can be mapped + * + * @param string $fieldName + * @param string $value + * @param MetaInformationInterface $metaInformation + * + * @return bool + */ + public function mapValue($fieldName, $value, MetaInformationInterface $metaInformation) + { + return true; + } } \ No newline at end of file diff --git a/Doctrine/Mapper/EntityMapper.php b/Doctrine/Mapper/EntityMapper.php index 683c8d75..abe92a35 100644 --- a/Doctrine/Mapper/EntityMapper.php +++ b/Doctrine/Mapper/EntityMapper.php @@ -2,25 +2,21 @@ namespace FS\SolrBundle\Doctrine\Mapper; use FS\SolrBundle\Doctrine\Hydration\HydrationModes; -use FS\SolrBundle\Doctrine\Hydration\Hydrator; +use FS\SolrBundle\Doctrine\Hydration\HydratorInterface; +use FS\SolrBundle\Doctrine\Mapper\Factory\DocumentFactory; use FS\SolrBundle\Doctrine\Mapper\Mapping\AbstractDocumentCommand; use FS\SolrBundle\Doctrine\Annotation\Index as Solr; use Solarium\QueryType\Update\Query\Document\Document; -class EntityMapper +class EntityMapper implements EntityMapperInterface { /** - * @var CreateDocumentCommandInterface - */ - private $mappingCommand = null; - - /** - * @var Hydrator + * @var HydratorInterface */ private $doctrineHydrator; /** - * @var Hydrator + * @var HydratorInterface */ private $indexHydrator; @@ -30,62 +26,56 @@ class EntityMapper private $hydrationMode = ''; /** - * @param Hydrator $doctrineHydrator - * @param Hydrator $indexHydrator + * @var MetaInformationFactory */ - public function __construct(Hydrator $doctrineHydrator, Hydrator $indexHydrator) - { - $this->doctrineHydrator = $doctrineHydrator; - $this->indexHydrator = $indexHydrator; + private $metaInformationFactory; - $this->hydrationMode = HydrationModes::HYDRATE_DOCTRINE; - } + /** + * @var DocumentFactory + */ + private $documentFactory; /** - * @param AbstractDocumentCommand $command + * @param HydratorInterface $doctrineHydrator + * @param HydratorInterface $indexHydrator + * @param MetaInformationFactory $metaInformationFactory */ - public function setMappingCommand(AbstractDocumentCommand $command) + public function __construct(HydratorInterface $doctrineHydrator, HydratorInterface $indexHydrator, MetaInformationFactory $metaInformationFactory) { - $this->mappingCommand = $command; + $this->doctrineHydrator = $doctrineHydrator; + $this->indexHydrator = $indexHydrator; + $this->metaInformationFactory = $metaInformationFactory; + $this->documentFactory = new DocumentFactory($metaInformationFactory); + + $this->hydrationMode = HydrationModes::HYDRATE_DOCTRINE; } /** - * @param MetaInformation $meta - * - * @return Document + * {@inheritdoc} */ - public function toDocument(MetaInformation $meta) + public function toDocument(MetaInformationInterface $metaInformation) { - if ($this->mappingCommand instanceof AbstractDocumentCommand) { - return $this->mappingCommand->createDocument($meta); - } - - return null; + return $this->documentFactory->createDocument($metaInformation); } /** - * @param \ArrayAccess $document - * @param object $sourceTargetEntity - * - * @return object - * - * @throws \InvalidArgumentException if $sourceTargetEntity is null + * {@inheritdoc} */ public function toEntity(\ArrayAccess $document, $sourceTargetEntity) { if (null === $sourceTargetEntity) { - throw new \InvalidArgumentException('$sourceTargetEntity should not be null'); + throw new SolrMappingException('$sourceTargetEntity should not be null'); } - $metaInformationFactory = new MetaInformationFactory(); - $metaInformation = $metaInformationFactory->loadInformation($sourceTargetEntity); + $metaInformation = $this->metaInformationFactory->loadInformation($sourceTargetEntity); - $hydratedDocument = $this->indexHydrator->hydrate($document, $metaInformation); - if ($this->hydrationMode == HydrationModes::HYDRATE_INDEX) { - return $hydratedDocument; + if ($metaInformation->isDoctrineEntity() === false && $this->hydrationMode == HydrationModes::HYDRATE_DOCTRINE) { + throw new SolrMappingException(sprintf('Please check your config. Given entity is not a Doctrine entity, but Doctrine hydration is enabled. Use setHydrationMode(HydrationModes::HYDRATE_DOCTRINE) to fix this.')); } - $metaInformation->setEntity($hydratedDocument); + if ($this->hydrationMode == HydrationModes::HYDRATE_INDEX) { + return $this->indexHydrator->hydrate($document, $metaInformation); + } if ($this->hydrationMode == HydrationModes::HYDRATE_DOCTRINE) { return $this->doctrineHydrator->hydrate($document, $metaInformation); diff --git a/Doctrine/Mapper/EntityMapperInterface.php b/Doctrine/Mapper/EntityMapperInterface.php new file mode 100644 index 00000000..054035cf --- /dev/null +++ b/Doctrine/Mapper/EntityMapperInterface.php @@ -0,0 +1,33 @@ +metaInformationFactory = $metaInformationFactory; + } + + /** + * @param MetaInformationInterface $metaInformation + * + * @return null|Document + * + * @throws SolrMappingException if no id is set + */ + public function createDocument(MetaInformationInterface $metaInformation) + { + $fields = $metaInformation->getFields(); + if (count($fields) == 0) { + return null; + } + + if (!$metaInformation->getEntityId() && !$metaInformation->generateDocumentId()) { + throw new SolrMappingException(sprintf('No entity id set for "%s"', $metaInformation->getClassName())); + } + + $documentId = $metaInformation->getDocumentKey(); + if ($metaInformation->generateDocumentId()) { + $documentId = $metaInformation->getDocumentName() . '_' . Uuid::uuid1()->toString(); + } + + $document = new Document(); + $document->setKey(MetaInformationInterface::DOCUMENT_KEY_FIELD_NAME, $documentId); + + $document->setBoost($metaInformation->getBoost()); + + foreach ($fields as $field) { + if (!$field instanceof Field) { + continue; + } + + $fieldValue = $field->getValue(); + if (($fieldValue instanceof Collection || is_array($fieldValue)) && $field->nestedClass) { + $this->mapCollectionField($document, $field, $metaInformation->getEntity()); + } 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) { // index object as "flat" string, call getter + $document->addField($field->getNameWithAlias(), $this->mapObjectField($field), $field->getBoost()); + } 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 { // field contains simple data-type + $document->addField($field->getNameWithAlias(), $fieldValue, $field->getBoost()); + } + + if ($field->getFieldModifier()) { + $document->setFieldModifier($field->getNameWithAlias(), $field->getFieldModifier()); + } + } + + return $document; + } + + /** + * @param Field $field + * + * @return array|string + * + * @throws SolrMappingException if getter return value is object + */ + private function mapObjectField(Field $field) + { + $value = $field->getValue(); + $getter = $field->getGetterName(); + 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); + + 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 $getterReturnValue; + } + + /** + * @param object $object + * @param string $getter + * + * @return mixed + * + * @throws SolrMappingException if given getter does not exists + */ + private function callGetterMethod($object, $getter) + { + $methodName = $getter; + if (strpos($getter, '(') !== false) { + $methodName = substr($getter, 0, strpos($getter, '(')); + } + + if (!method_exists($object, $methodName)) { + throw new SolrMappingException(sprintf('No method "%s()" found in class "%s"', $methodName, get_class($object))); + } + + $method = new \ReflectionMethod($object, $methodName); + // getter with arguments + if (strpos($getter, ')') !== false) { + $getterArguments = explode(',', substr($getter, strpos($getter, '(') + 1, -1)); + $getterArguments = array_map(function ($parameter) { + return trim(preg_replace('#[\'"]#', '', $parameter)); + }, $getterArguments); + + return $method->invokeArgs($object, $getterArguments); + } + + return $method->invoke($object); + } + + /** + * @param Field $field + * @param string $sourceTargetClass + * + * @return array + * + * @throws SolrMappingException if no getter method was found + */ + private function mapCollectionField($document, Field $field, $sourceTargetObject) + { + /** @var Collection $collection */ + $collection = $field->getValue(); + $getter = $field->getGetterName(); + + if ($getter != '') { + $collection = $this->callGetterMethod($sourceTargetObject, $getter); + + $collection = array_filter($collection, function ($value) { + return $value !== null; + }); + } + + $values = []; + if (count($collection)) { + foreach ($collection as $relatedObj) { + 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/Mapping/AbstractDocumentCommand.php b/Doctrine/Mapper/Mapping/AbstractDocumentCommand.php deleted file mode 100644 index 25364683..00000000 --- a/Doctrine/Mapper/Mapping/AbstractDocumentCommand.php +++ /dev/null @@ -1,28 +0,0 @@ -addField('id', $meta->getEntityId()); - $document->addField('document_name_s', $meta->getDocumentName()); - $document->setBoost($meta->getBoost()); - - return $document; - } -} diff --git a/Doctrine/Mapper/Mapping/CommandFactory.php b/Doctrine/Mapper/Mapping/CommandFactory.php deleted file mode 100644 index b0f0b632..00000000 --- a/Doctrine/Mapper/Mapping/CommandFactory.php +++ /dev/null @@ -1,36 +0,0 @@ -commands)) { - throw new \RuntimeException(sprintf('%s is an unknown command', $command)); - } - - return $this->commands[$command]; - } - - /** - * @param AbstractDocumentCommand $command - * @param string $commandName - */ - public function add(AbstractDocumentCommand $command, $commandName) - { - $this->commands[$commandName] = $command; - } -} diff --git a/Doctrine/Mapper/Mapping/MapAllFieldsCommand.php b/Doctrine/Mapper/Mapping/MapAllFieldsCommand.php deleted file mode 100644 index 11bf81b6..00000000 --- a/Doctrine/Mapper/Mapping/MapAllFieldsCommand.php +++ /dev/null @@ -1,39 +0,0 @@ -getFields(); - if (count($fields) == 0) { - return null; - } - - $document = parent::createDocument($meta); - - foreach ($fields as $field) { - if (!$field instanceof Field) { - continue; - } - - $document->addField($field->getNameWithAlias(), $field->getValue(), $field->getBoost()); - } - - return $document; - } -} diff --git a/Doctrine/Mapper/Mapping/MapIdentifierCommand.php b/Doctrine/Mapper/Mapping/MapIdentifierCommand.php deleted file mode 100644 index d12966b9..00000000 --- a/Doctrine/Mapper/Mapping/MapIdentifierCommand.php +++ /dev/null @@ -1,9 +0,0 @@ -entity !== null) { + if ($this->entity !== null && $this->entity->getId()) { return $this->entity->getId(); } - return 0; + return $this->entityId; } /** - * @return string + * @param int $entityId + */ + public function setEntityId($entityId) + { + $this->entityId = $entityId; + } + + /** + * {@inheritdoc} */ public function getIdentifier() { @@ -78,7 +109,7 @@ public function getIdentifier() } /** - * @return string + * {@inheritdoc} */ public function getClassName() { @@ -86,7 +117,7 @@ public function getClassName() } /** - * @return string + * {@inheritdoc} */ public function getDocumentName() { @@ -94,7 +125,7 @@ public function getDocumentName() } /** - * @return array With instances of FS\SolrBundle\Doctrine\Annotation\Field + * {@inheritdoc} */ public function getFields() { @@ -102,7 +133,7 @@ public function getFields() } /** - * @return string + * {@inheritdoc} */ public function getRepository() { @@ -110,7 +141,7 @@ public function getRepository() } /** - * @return object + * {@inheritdoc} */ public function getEntity() { @@ -118,7 +149,7 @@ public function getEntity() } /** - * @param string $identifiert + * @param Id $identifier */ public function setIdentifier($identifier) { @@ -142,7 +173,7 @@ public function setDocumentName($documentName) } /** - * @param multitype: $fields + * @param Field[] $fields */ public function setFields($fields) { @@ -150,38 +181,57 @@ public function setFields($fields) } /** - * @param string $field + * @param string $fieldName + * * @return boolean */ - public function hasField($field) + public function hasField($fieldName) { - if (count($this->fields) == 0) { + $fields = array_filter($this->fields, function(Field $field) use ($fieldName) { + return $field->name == $fieldName || $field->getNameWithAlias() == $fieldName; + }); + + if (count($fields) == 0) { return false; } - return isset($this->fields[$field]); + return true; } /** - * @param string $field + * @param string $fieldName * @param string $value + * + * @throws SolrMappingException if $fieldName does not exist */ - public function setFieldValue($field, $value) + public function setFieldValue($fieldName, $value) { - $this->fields[$field]->value = $value; + if ($this->hasField($fieldName) == false) { + throw new SolrMappingException(sprintf('Field %s does not exist', $fieldName)); + } + + $field = $this->getField($fieldName); + $field->value = $value; } /** - * @param unknown_type $field - * @return Field|null + * {@inheritdoc} */ - public function getField($field) + public function getField($fieldName) { - if (!$this->hasField($field)) { + if ($fieldName == '') { + throw new SolrMappingException('$fieldName must not be empty'); + } + + if (!$this->hasField($fieldName)) { return null; } - return $this->fields[$field]; + $fields = array_filter($this->fields, function(Field $field) use ($fieldName) { + return $field->name == $fieldName || $field->getNameWithAlias() == $fieldName; + }); + + return array_pop($fields); } /** @@ -201,7 +251,7 @@ public function setEntity($entity) } /** - * @return array + * {@inheritdoc} */ public function getFieldMapping() { @@ -217,7 +267,7 @@ public function setFieldMapping($fieldMapping) } /** - * @return number + * {@inheritdoc} */ public function getBoost() { @@ -245,7 +295,7 @@ public function hasSynchronizationFilter() } /** - * @return string + * {@inheritdoc} */ public function getSynchronizationCallback() { @@ -269,10 +319,86 @@ public function setIndex($index) } /** - * @return string + * {@inheritdoc} */ public function getIndex() { return $this->index; } + + /** + * {@inheritdoc} + */ + public function getDocumentKey() + { + return $this->documentName . '_' . $this->getEntityId(); + } + + /** + * @return boolean + */ + public function isDoctrineEntity() + { + return $this->isDoctrineEntity; + } + + /** + * @param boolean $isDoctrineEntity + */ + public function setIsDoctrineEntity($isDoctrineEntity) + { + $this->isDoctrineEntity = $isDoctrineEntity; + } + + /** + * {@inheritdoc} + */ + public function getIdentifierFieldName() + { + return $this->identifier->name; + } + + /** + * @return string + */ + public function getDoctrineMapperType() + { + return $this->doctrineMapperType; + } + + /** + * @param string $doctrineMapperType + */ + public function setDoctrineMapperType($doctrineMapperType) + { + $this->doctrineMapperType = $doctrineMapperType; + } + + /** + * {@inheritdoc} + */ + public function generateDocumentId() + { + if ($this->identifier == null) { + throw new SolrMappingException('No identifier is set'); + } + + return $this->identifier->generateId; + } + + /** + * {@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 f91aa3cb..071b219f 100644 --- a/Doctrine/Mapper/MetaInformationFactory.php +++ b/Doctrine/Mapper/MetaInformationFactory.php @@ -3,21 +3,12 @@ use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; use FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolver; -use FS\SolrBundle\Doctrine\Configuration; /** - * - * @author fs - * + * instantiates a new MetaInformation object by a given entity */ class MetaInformationFactory { - - /** - * @var MetaInformation - */ - private $metaInformations = null; - /** * @var AnnotationReader */ @@ -28,9 +19,12 @@ class MetaInformationFactory */ private $classnameResolver = null; - public function __construct() + /** + * @param AnnotationReader $reader + */ + public function __construct(AnnotationReader $reader) { - $this->annotationReader = new AnnotationReader(); + $this->annotationReader = $reader; } /** @@ -42,40 +36,102 @@ public function setClassnameResolver(ClassnameResolver $classnameResolver) } /** - * @param $entity + * @param object|string $entity entity, entity-alias or classname * * @return MetaInformation * - * @throws \RuntimeException if no declaration for document found in $entity + * @throws SolrMappingException if no declaration for document found in $entity */ public function loadInformation($entity) { - $className = $this->getClass($entity); if (!is_object($entity)) { - $entity = new $className; + $reflectionClass = new \ReflectionClass($className); + if (!$reflectionClass->isInstantiable()) { + throw new SolrMappingException(sprintf('Cannot instantiate entity %s', $className)); + } + $entity = $reflectionClass->newInstanceWithoutConstructor(); } if (!$this->annotationReader->hasDocumentDeclaration($entity)) { - throw new \RuntimeException(sprintf('no declaration for document found in entity %s', $className)); + 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)); $metaInformation->setSynchronizationCallback($this->annotationReader->getSynchronizationCallback($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; } + /** + * @param object $entity + * + * @return bool + */ + private function isDoctrineEntity($entity) + { + if ($this->annotationReader->isOrm($entity) || $this->annotationReader->isOdm($entity)) { + return true; + } + + return false; + } + + /** + * @param object $entity + * + * @return string + */ + private function getDoctrineMapperType($entity) + { + if ($this->isDoctrineEntity($entity) == false) { + return ''; + } + + if ($this->annotationReader->isOdm($entity)) { + return MetaInformationInterface::DOCTRINE_MAPPER_TYPE_DOCUMENT; + } + + if ($this->annotationReader->isOrm($entity)) { + return MetaInformationInterface::DOCTRINE_MAPPER_TYPE_RELATIONAL; + } + } + /** * @param object $entity * diff --git a/Doctrine/Mapper/MetaInformationInterface.php b/Doctrine/Mapper/MetaInformationInterface.php new file mode 100644 index 00000000..01f425d7 --- /dev/null +++ b/Doctrine/Mapper/MetaInformationInterface.php @@ -0,0 +1,145 @@ + exampleentity + * + * @return string + */ + public function getDocumentName(); + + /** + * @return Field[] + */ + public function getFields(); + + /** + * Returns full qualified classname of repository-class + * + * @return string + */ + public function getRepository(); + + /** + * Source/target entity instance + * + * @return object + */ + public function getEntity(); + + /** + * @param string $fieldName + * + * @return Field|null + * + * @throws \InvalidArgumentException if given $fieldName is unknown + */ + public function getField($fieldName); + + /** + * @return array + */ + public function getFieldMapping(); + + /** + * The document boost value + * + * @return number + */ + public function getBoost(); + + /** + * @return string + */ + public function getSynchronizationCallback(); + + /** + * @return boolean + */ + 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(); + + /** + * Returns combination of DOCUMENT_KEY_FIELD_NAME and entity-id + * + * @return string + */ + 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(); + + /** + * @return bool + */ + public function isNested(); +} \ No newline at end of file diff --git a/Doctrine/Mapper/SolrMappingException.php b/Doctrine/Mapper/SolrMappingException.php new file mode 100644 index 00000000..254c16eb --- /dev/null +++ b/Doctrine/Mapper/SolrMappingException.php @@ -0,0 +1,8 @@ +solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function postPersist(LifecycleEventArgs $args) - { - $entity = $args->getDocument(); - - try { - $this->solr->addDocument($entity); - } catch (\RuntimeException $e) { - } - } -} diff --git a/Doctrine/ODM/Listener/DeleteDocumentListener.php b/Doctrine/ODM/Listener/DeleteDocumentListener.php deleted file mode 100644 index 96b19ba4..00000000 --- a/Doctrine/ODM/Listener/DeleteDocumentListener.php +++ /dev/null @@ -1,38 +0,0 @@ -solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function preRemove(LifecycleEventArgs $args) - { - $entity = $args->getDocument(); - - try { - $this->solr->removeDocument($entity); - } catch (\RuntimeException $e) { - } - } -} - -?> \ No newline at end of file diff --git a/Doctrine/ODM/Listener/DocumentIndexerSubscriber.php b/Doctrine/ODM/Listener/DocumentIndexerSubscriber.php new file mode 100644 index 00000000..a071603b --- /dev/null +++ b/Doctrine/ODM/Listener/DocumentIndexerSubscriber.php @@ -0,0 +1,68 @@ +getDocument(); + + try { + $doctrineChangeSet = $args->getDocumentManager()->getUnitOfWork()->getDocumentChangeSet($document); + + if ($this->hasChanged($doctrineChangeSet, $document) == false) { + return; + } + + $this->solr->updateDocument($document); + } catch (\RuntimeException $e) { + $this->logger->debug($e->getMessage()); + } + } + + /** + * @param LifecycleEventArgs $args + */ + public function preRemove(LifecycleEventArgs $args) + { + $entity = $args->getDocument(); + + try { + $this->solr->removeDocument($entity); + } catch (\RuntimeException $e) { + $this->logger->debug($e->getMessage()); + } + } + + /** + * @param LifecycleEventArgs $args + */ + public function postPersist(LifecycleEventArgs $args) + { + $entity = $args->getDocument(); + + try { + $this->solr->addDocument($entity); + } catch (\RuntimeException $e) { + $this->logger->debug($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/Doctrine/ODM/Listener/UpdateDocumentListener.php b/Doctrine/ODM/Listener/UpdateDocumentListener.php deleted file mode 100644 index 551c0400..00000000 --- a/Doctrine/ODM/Listener/UpdateDocumentListener.php +++ /dev/null @@ -1,36 +0,0 @@ -solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function postUpdate(LifecycleEventArgs $args) - { - $entity = $args->getDocument(); - - try { - $this->solr->updateDocument($entity); - } catch (\RuntimeException $e) { - } - } -} diff --git a/Doctrine/ORM/Listener/AddDocumentListener.php b/Doctrine/ORM/Listener/AddDocumentListener.php deleted file mode 100644 index 8704ea96..00000000 --- a/Doctrine/ORM/Listener/AddDocumentListener.php +++ /dev/null @@ -1,35 +0,0 @@ -solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function postPersist(LifecycleEventArgs $args) - { - $entity = $args->getEntity(); - - try { - $this->solr->addDocument($entity); - } catch (\RuntimeException $e) { - } - } -} diff --git a/Doctrine/ORM/Listener/DeleteDocumentListener.php b/Doctrine/ORM/Listener/DeleteDocumentListener.php deleted file mode 100644 index ff97de24..00000000 --- a/Doctrine/ORM/Listener/DeleteDocumentListener.php +++ /dev/null @@ -1,35 +0,0 @@ -solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function preRemove(LifecycleEventArgs $args) - { - $entity = $args->getEntity(); - - try { - $this->solr->removeDocument($entity); - } catch (\RuntimeException $e) { - } - } -} diff --git a/Doctrine/ORM/Listener/EntityIndexerSubscriber.php b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php new file mode 100644 index 00000000..80f72c3e --- /dev/null +++ b/Doctrine/ORM/Listener/EntityIndexerSubscriber.php @@ -0,0 +1,126 @@ +getEntity(); + + if ($this->isAbleToIndex($entity) === false) { + return; + } + + $doctrineChangeSet = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($entity); + try { + if ($this->hasChanged($doctrineChangeSet, $entity) === false) { + return; + } + + $this->solr->updateDocument($entity); + } catch (\Exception $e) { + $this->logger->debug($e->getMessage()); + } + } + + /** + * @param LifecycleEventArgs $args + */ + public function postPersist(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + if ($this->isAbleToIndex($entity) === false) { + return; + } + + $this->persistedEntities[] = $entity; + } + + /** + * @param LifecycleEventArgs $args + */ + public function preRemove(LifecycleEventArgs $args) + { + $entity = $args->getEntity(); + + if ($this->isAbleToIndex($entity) === false) { + return; + } + + if ($this->isNested($entity)) { + $this->deletedNestedEntities[] = $this->emptyCollections($entity); + } else { + $this->deletedRootEntities[] = $this->emptyCollections($entity); + } + } + + /** + * @param object $object + * + * @return object + */ + private function emptyCollections($object) + { + $deepcopy = new DeepCopy(); + $deepcopy->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); + + return $deepcopy->copy($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); + } + $this->deletedNestedEntities = []; + } +} \ No newline at end of file diff --git a/Doctrine/ORM/Listener/UpdateDocumentListener.php b/Doctrine/ORM/Listener/UpdateDocumentListener.php deleted file mode 100644 index 0d8fe0d6..00000000 --- a/Doctrine/ORM/Listener/UpdateDocumentListener.php +++ /dev/null @@ -1,35 +0,0 @@ -solr = $solr; - } - - /** - * @param LifecycleEventArgs $args - */ - public function postUpdate(LifecycleEventArgs $args) - { - $entity = $args->getEntity(); - - try { - $this->solr->updateDocument($entity); - } catch (\RuntimeException $e) { - } - } -} diff --git a/Event/ErrorEvent.php b/Event/ErrorEvent.php index c690f0fd..1eb09e2b 100644 --- a/Event/ErrorEvent.php +++ b/Event/ErrorEvent.php @@ -1,6 +1,10 @@ sourceEvent; } + /** + * @return bool + */ public function hasSourceEvent() { return $this->sourceEvent !== null; diff --git a/Event/Events.php b/Event/Events.php index 360c0923..9020547e 100644 --- a/Event/Events.php +++ b/Event/Events.php @@ -2,6 +2,9 @@ namespace FS\SolrBundle\Event; +/** + * List of event which can be fired + */ final class Events { const PRE_INSERT = 'solr.pre_insert'; diff --git a/Event/Listener/AbstractLogListener.php b/Event/Listener/AbstractLogListener.php index 8d9cb6ea..1bcf5b81 100644 --- a/Event/Listener/AbstractLogListener.php +++ b/Event/Listener/AbstractLogListener.php @@ -1,8 +1,7 @@ getDocumentName() . ':' . $metaInformation->getEntityId(); } /** - * @param MetaInformation $metaInformation + * @param MetaInformationInterface $metaInformation + * * @return string */ - protected function createFieldList(MetaInformation $metaInformation) + protected function createFieldList(MetaInformationInterface $metaInformation) { return implode(', ', $metaInformation->getFields()); } diff --git a/Event/Listener/ClearIndexLogListener.php b/Event/Listener/ClearIndexLogListener.php index 5a8d844a..b7508bb2 100644 --- a/Event/Listener/ClearIndexLogListener.php +++ b/Event/Listener/ClearIndexLogListener.php @@ -2,11 +2,16 @@ namespace FS\SolrBundle\Event\Listener; - use FS\SolrBundle\Event\Event; +/** + * Create a log-entry if the index was cleared + */ class ClearIndexLogListener extends AbstractLogListener { + /** + * @param Event $event + */ public function onClearIndex(Event $event) { $this->logger->debug(sprintf('clear index')); diff --git a/Event/Listener/DeleteLogListener.php b/Event/Listener/DeleteLogListener.php index a6f9e90c..30d69cd7 100644 --- a/Event/Listener/DeleteLogListener.php +++ b/Event/Listener/DeleteLogListener.php @@ -1,8 +1,12 @@ getExceptionMessage(); diff --git a/Event/Listener/InsertLogListener.php b/Event/Listener/InsertLogListener.php index 6d2e6e76..162f908d 100644 --- a/Event/Listener/InsertLogListener.php +++ b/Event/Listener/InsertLogListener.php @@ -3,6 +3,9 @@ use FS\SolrBundle\Event\Event; +/** + * Create a log-entry if a document was insert + */ class InsertLogListener extends AbstractLogListener { diff --git a/Event/Listener/SynchronizationSummaryListener.php b/Event/Listener/SynchronizationSummaryListener.php deleted file mode 100644 index 4d965362..00000000 --- a/Event/Listener/SynchronizationSummaryListener.php +++ /dev/null @@ -1,37 +0,0 @@ -commandResult = $commandResult; - $this->resultFactory = $resultFactory; - } - - public function onSolrError(Event $event) - { - if ($event instanceof ErrorEvent) { - $this->commandResult->error( - $this->resultFactory->fromEvent($event) - ); - } - } - - public function onSolrSuccess(Event $event) - { - $this->commandResult->success( - $this->resultFactory->fromEvent($event) - ); - } -} \ No newline at end of file diff --git a/Event/Listener/UpdateLogListener.php b/Event/Listener/UpdateLogListener.php index d9f4960e..cf052755 100644 --- a/Event/Listener/UpdateLogListener.php +++ b/Event/Listener/UpdateLogListener.php @@ -1,8 +1,12 @@ addCompilerPass(new AddCreateDocumentCommandPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); - } + $container->addCompilerPass(new AddSolariumPluginsPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); + } } 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/Logging/DebugLogger.php b/Logging/DebugLogger.php new file mode 100644 index 00000000..e58c19f0 --- /dev/null +++ b/Logging/DebugLogger.php @@ -0,0 +1,52 @@ +queries; + } + + /** + * {@inheritdoc} + */ + public function startRequest(array $request) + { + $this->start = microtime(true); + $this->queries[++$this->currentQuery] = [ + 'request' => $request, + 'executionMS' => 0 + ]; + } + + /** + * {@inheritdoc} + */ + public function stopRequest() + { + $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; + } +} \ No newline at end of file diff --git a/Logging/SolrLoggerInterface.php b/Logging/SolrLoggerInterface.php new file mode 100644 index 00000000..324d1347 --- /dev/null +++ b/Logging/SolrLoggerInterface.php @@ -0,0 +1,18 @@ +metaInformation; + } + + /** + * @param MetaInformationInterface $metaInformation + */ + public function setMetaInformation($metaInformation) + { + $this->metaInformation = $metaInformation; + + $this->entity = $metaInformation->getClassName(); + $this->index = $metaInformation->getIndex(); + } + + /** + * @return string */ public function getEntity() { @@ -32,7 +65,7 @@ public function getEntity() } /** - * @param object $entity + * @param string $entity */ public function setEntity($entity) { @@ -40,7 +73,7 @@ public function setEntity($entity) } /** - * @param \Solarium\QueryType\Update\Query\Document\Document $document + * @param Document $document */ public function setDocument($document) { @@ -48,7 +81,7 @@ public function setDocument($document) } /** - * @return \Solarium\QueryType\Update\Query\Document\Document + * @return Document */ public function getDocument() { @@ -56,15 +89,15 @@ public function getDocument() } /** - * @param \FS\SolrBundle\Solr $solr + * @param SolrInterface $solr */ - public function setSolr($solr) + public function setSolr(SolrInterface $solr) { $this->solr = $solr; } /** - * @return \FS\SolrBundle\Solr + * @return SolrInterface */ public function getSolr() { @@ -80,4 +113,20 @@ public function setHydrationMode($mode) { $this->getSolr()->getMapper()->setHydrationMode($mode); } + + /** + * @return string + */ + public function getIndex() + { + return $this->index; + } + + /** + * @param string $index + */ + public function setIndex($index) + { + $this->index = $index; + } } diff --git a/Query/DeleteDocumentQuery.php b/Query/DeleteDocumentQuery.php new file mode 100644 index 00000000..ab4582ff --- /dev/null +++ b/Query/DeleteDocumentQuery.php @@ -0,0 +1,39 @@ +documentKey = $documentKey; + } + + /** + * @return string + * + * @throws QueryException when id or document_name is null + */ + public function getQuery() + { + $idField = $this->documentKey; + + if ($idField == null) { + throw new QueryException('id should not be null'); + } + + $this->setQuery(sprintf('id:%s', $idField)); + + return parent::getQuery(); + } +} \ No newline at end of file diff --git a/Query/Exception/QueryException.php b/Query/Exception/QueryException.php new file mode 100644 index 00000000..6c1806fd --- /dev/null +++ b/Query/Exception/QueryException.php @@ -0,0 +1,8 @@ +documentName = $documentName; + } + /** * @return string * - * @throws \RuntimeException if documentName is null + * @throws QueryException if documentName is null */ public function getQuery() { - $documentNameField = $this->document->document_name_s; + $documentName = $this->documentName; - if ($documentNameField == null) { - throw new \RuntimeException('documentName should not be null'); + if ($documentName == null) { + throw new QueryException('documentName should not be null'); } - $query = sprintf('document_name_s:%s', $documentNameField); + $documentLimitation = $this->createFilterQuery('id')->setQuery(sprintf('id:%s_*', $documentName)); + $this->addFilterQuery($documentLimitation); - $this->setQuery($query); + $this->setQuery('*:*'); return parent::getQuery(); } diff --git a/Query/FindByIdentifierQuery.php b/Query/FindByIdentifierQuery.php index 32676517..82af8da9 100644 --- a/Query/FindByIdentifierQuery.php +++ b/Query/FindByIdentifierQuery.php @@ -1,28 +1,41 @@ documentKey = $documentKey; + } /** * @return string - * @throws \RuntimeException when id or document_name is null + * + * @throws QueryException when id or document_name is null */ public function getQuery() { - $idField = $this->document->id; - $documentNameField = $this->document->document_name_s; + $idField = $this->documentKey; if ($idField == null) { - throw new \RuntimeException('id should not be null'); + throw new QueryException('id should not be null'); } - if ($documentNameField == null) { - throw new \RuntimeException('documentName should not be null'); - } + $documentLimitation = $this->createFilterQuery('id')->setQuery(sprintf('id:%s', $idField)); + $this->addFilterQuery($documentLimitation); - $query = sprintf('id:%s AND document_name_s:%s', $idField, $documentNameField); - $this->setQuery($query); + $this->setQuery('*:*'); return parent::getQuery(); } diff --git a/Query/QueryBuilder.php b/Query/QueryBuilder.php new file mode 100644 index 00000000..841d5a7e --- /dev/null +++ b/Query/QueryBuilder.php @@ -0,0 +1,332 @@ +solr = $solr; + $this->metaInformation = $metaInformation; + } + + /** + * {@inheritdoc} + */ + public function where($field) + { + $solrField = $this->metaInformation->getField($field); + if ($solrField === null) { + throw new UnknownFieldException(sprintf('Field %s does not exists', $field)); + } + + $fieldName = $solrField->getNameWithAlias(); + + $this->criteria = Criteria::where($fieldName); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function andWhere($field) + { + if ($field instanceof QueryBuilder) { + $this->criteria = $this->criteria->andWhere($field->getCriteria()); + + return $this; + } + + $solrField = $this->metaInformation->getField($field); + if ($solrField === null) { + throw new UnknownFieldException(sprintf('Field %s does not exists', $field)); + } + + $fieldName = $solrField->getNameWithAlias(); + + $this->criteria = $this->criteria->andWhere($fieldName); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function orWhere($field) + { + if ($field instanceof QueryBuilder) { + $this->criteria = $this->criteria->orWhere($field->getCriteria()); + + return $this; + } + + $solrField = $this->metaInformation->getField($field); + if ($solrField === null) { + throw new UnknownFieldException(sprintf('Field %s does not exists', $field)); + } + + $fieldName = $solrField->getNameWithAlias(); + + $this->criteria = $this->criteria->orWhere($fieldName); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function is($value) + { + $this->criteria = $this->criteria->is($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function between($lowerBound, $upperBound, $includeLowerBound = true, $includeUpperBound = true) + { + $this->criteria = $this->criteria->between($lowerBound, $upperBound, $includeLowerBound, $includeUpperBound); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function in(array $values) + { + $this->criteria = $this->criteria->in($values); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function withinCircle($latitude, $longitude, $distance) + { + $this->criteria = $this->criteria->withinCircle($latitude, $longitude, $distance); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function withinBox($startLatitude, $startLongitude, $endLatitude, $endLongitude) + { + $this->criteria = $this->criteria->withinBox($startLatitude, $startLongitude, $endLatitude, $endLongitude); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function nearCircle($latitude, $longitude, $distance) + { + $this->criteria = $this->criteria->nearCircle($latitude, $longitude, $distance); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isNull() + { + $this->criteria = $this->criteria->isNull(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isNotNull() + { + $this->criteria = $this->criteria->isNotNull(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function contains($value) + { + $this->criteria = $this->criteria->contains($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function startsWith($prefix) + { + $this->criteria = $this->criteria->startsWith($prefix); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function endsWith($postfix) + { + $this->criteria = $this->criteria->endsWith($postfix); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function not() + { + $this->criteria = $this->criteria->not(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function notOperator() + { + $this->criteria = $this->criteria->notOperator(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function fuzzy($value, $levenshteinDistance = null) + { + $this->criteria = $this->criteria->fuzzy($value, $levenshteinDistance); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function sloppy($phrase, $distance) + { + $this->criteria = $this->criteria->sloppy($phrase, $distance); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expression($value) + { + $this->criteria = $this->criteria->expression($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function greaterThanEqual($value) + { + $this->criteria = $this->criteria->greaterThanEqual($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function greaterThan($value) + { + $this->criteria = $this->criteria->greaterThan($value); + return $this; + } + + /** + * {@inheritdoc} + */ + public function lessThanEqual($value) + { + $this->criteria = $this->criteria->lessThanEqual($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function lessThan($value) + { + $this->criteria = $this->criteria->lessThan($value); + return $this; + } + + /** + * {@inheritdoc} + */ + public function boost($value) + { + $this->criteria = $this->criteria->boost($value); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getQuery() + { + $query = new SolrQuery(); + $query->setSolr($this->solr); + $query->setRows(1000000); + $query->setCustomQuery($this->criteria->getQuery()); + $query->setIndex($this->metaInformation->getIndex()); + $query->setEntity($this->metaInformation->getEntity()); + $query->setMetaInformation($this->metaInformation); + + return $query; + } + + /** + * @return Criteria + */ + public function getCriteria() + { + return $this->criteria; + } +} diff --git a/Query/QueryBuilderInterface.php b/Query/QueryBuilderInterface.php new file mode 100644 index 00000000..54d3dc32 --- /dev/null +++ b/Query/QueryBuilderInterface.php @@ -0,0 +1,191 @@ +setUseAndOperator(false); foreach ($this->mappedFields as $documentField => $entityField) { + if ($documentField == $this->getMetaInformation()->getIdentifierFieldName()) { + continue; + } + $this->searchTerms[$documentField] = $value; } } @@ -109,15 +121,34 @@ public function queryAllFields($value) * * @param string $field * @param string $value + * * @return SolrQuery + * + * @throws UnknownFieldException if $field has not mapping / is unknown */ public function addSearchTerm($field, $value) { $documentFieldsAsValues = array_flip($this->mappedFields); - if (array_key_exists($field, $documentFieldsAsValues)) { - $documentFieldName = $documentFieldsAsValues[$field]; + $classname = $this->getMetaInformation()->getClassName(); + + if (!array_key_exists($field, $documentFieldsAsValues)) { + throw new UnknownFieldException(sprintf('Entity %s has no mapping for field %s', $classname, $field)); + } + + $documentFieldName = $documentFieldsAsValues[$field]; + 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); + $childFilterPhrase = str_replace('"', '*', $value); + $childFilterPhrase = str_replace(' ', '*', $value); + $childFilterPhrase = str_replace('\*', '*', $value); + $this->childQueries[$documentFieldName] = $childFilterPhrase; + } else { $this->searchTerms[$documentFieldName] = $value; } @@ -126,6 +157,7 @@ public function addSearchTerm($field, $value) /** * @param string $field + * * @return SolrQuery */ public function addField($field) @@ -138,19 +170,43 @@ public function addField($field) return $this; } + /** + * {@inheritdoc} + */ + public function addFilterQuery($filterQuery) + { + if ($this->getFilterQuery('id')) { + return $this; + } + + return parent::addFilterQuery($filterQuery); + } + /** * @return string */ public function getQuery() { + $searchTerms = array_merge($this->searchTerms, $this->childQueries); + + $keyField = $this->getMetaInformation()->getDocumentKey(); + + $documentLimitation = $this->createFilterQuery('id')->setQuery('id:'.$keyField.'*'); + + $this->addFilterQuery($documentLimitation); if ($this->customQuery) { - $this->setQuery($this->customQuery); + parent::setQuery($this->customQuery); + return $this->customQuery; } $term = ''; - if (count($this->searchTerms) == 0) { - return $term; + // query all documents if no terms exists + if (count($searchTerms) == 0) { + $query = '*:*'; + parent::setQuery($query); + + return $query; } $logicOperator = 'AND'; @@ -159,24 +215,71 @@ 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); + + $termCount++; - if ($this->useWildcards) { - $term .= $fieldName . ':*' . $fieldValue . '*'; - } else { - $term .= $fieldName . ':' . $fieldValue; + continue; } - if ($termCount < count($this->searchTerms)) { + $fieldValue = $this->querifyFieldValue($fieldValue); + + $term .= $fieldName . ':' . $fieldValue; + + if ($termCount < count($searchTerms)) { $term .= ' ' . $logicOperator . ' '; } $termCount++; } + if (strlen($term) == 0) { + $term = '*:*'; + } + $this->setQuery($term); return $term; } + /** + * Transforms array to string representation and adds quotes + * + * @param string $fieldValue + * + * @return string + */ + private function querifyFieldValue($fieldValue) + { + if (is_array($fieldValue) && count($fieldValue) > 1) { + sort($fieldValue); + + $quoted = array_map(function($value) { + return '"'. $value .'"'; + }, $fieldValue); + + $fieldValue = implode(' TO ', $quoted); + $fieldValue = '['. $fieldValue . ']'; + + return $fieldValue; + } + + if (is_array($fieldValue) && count($fieldValue) === 1) { + $fieldValue = array_pop($fieldValue); + } + + if ($this->useWildcards) { + $fieldValue = '*' . $fieldValue . '*'; + } + + $termParts = explode(' ', $fieldValue); + if (count($termParts) > 1) { + $fieldValue = '"'.$fieldValue.'"'; + } + + return $fieldValue; + } } diff --git a/README.md b/README.md index f9045b78..7abfcc60 100644 --- a/README.md +++ b/README.md @@ -1,297 +1,441 @@ - +SolrBundle +========== [![Build Status](https://secure.travis-ci.org/floriansemm/SolrBundle.png?branch=master)](http://travis-ci.org/floriansemm/SolrBundle) [![Latest Stable Version](https://poser.pugx.org/floriansemm/solr-bundle/v/stable.svg)](https://packagist.org/packages/floriansemm/solr-bundle) [![Total Downloads](https://poser.pugx.org/floriansemm/solr-bundle/downloads.svg)](https://packagist.org/packages/floriansemm/solr-bundle) +Introduction +------------ + This Bundle provides a simple API to index and query a Solr Index. -# Configuration +## Installation + +Installation is a quick (I promise!) 3 step process: + +1. Download SolrBundle +2. Enable the Bundle +3. Configure the SolrBundle +4. configure your entity + +### Step 1: Download SolrBundle + +This bundle is available on Packagist. You can install it using Composer: + +```bash +$ composer require floriansemm/solr-bundle +``` + +### Step 2: Enable the bundle + +Next, enable the bundle in the kernel: + +``` php +language == 'en') { + return 'core0'; } - - B. or manually, in app/autoload.php - - i. In symfony 2.1.4 (supposing you clone the bundle in vendor/floriansemm/solr-bundle/FS/, making available vendor/floriansemm/solr-bundle/FS/SolrBundle/FSSolrBundle.php) - - $loader->add('FS\\SolrBundle', array(__DIR__.'/../vendor/floriansemm/solr-bundle')); - - ii. in older version it could be - - $loader->registerNamespaces(array( - // ... - 'FS' => __DIR__.'/../vendor/bundles', - // ... - )); - -## Multiple Indexes - -You have to setup the connection options - - # app/config/config.yml - fs_solr: - endpoints: - core1: - host: host - port: 8983 - path: /solr/core1 - core: corename - timeout: 5 - core1: - host: host - port: 8983 - path: /solr/core1 - core: corename - timeout: 5 - -With this config you can setup two cores: `core1` and `core2`. See section `Specify cores` for more information. - -# Usage # - -## Annotations - -To put an entity to the index, you must add some annotations to your entity: - - // your Entity - - // .... - use FS\SolrBundle\Doctrine\Annotation as Solr; - - /** - * @Solr\Document(repository="Full\Qualified\Class\Name") - * @ORM\Table() - */ - class Post - { - /** - * @Solr\Id - * - * @ORM\Column(name="id", type="integer") - * @ORM\Id - * @ORM\GeneratedValue(strategy="AUTO") - */ - - private $id; - /** - * - * @Solr\Field(type="string") - * - * @ORM\Column(name="title", type="string", length=255) - */ - private $title = ''; - - /** - * - * @Solr\Field(type="string") - * - * @ORM\Column(name="text", type="text") - */ - private $text = ''; - - /** - * @Solr\Field(type="date") - * - * @ORM\Column(name="created_at", type="datetime") - */ - private $created_at = null; - } - -### Supported field types - -Currently is a basic set of types implemented. - -- string -- text -- date -- integer -- float -- double -- long -- boolean - -It is possible to use custum field types (schema.xml). - -### Filter annotation - -In some cases a entity should not be index. For this you have the `SynchronizationFilter` Annotation. - - - /** - * @Solr\Document - * @Solr\SynchronizationFilter(callback="shouldBeIndex") - */ - class SomeEntity - { - /** - * @return boolean - */ - public function shouldBeIndex() - { - // put your logic here - } - } - -The callback property specifies an callable function, which decides whether the should index or not. - -### Specify cores - -It is possible to specify a core dedicated to a document - - /** - * @Solr\Document(index="core0") - */ - class SomeEntity - { - // ... - } - -All documents will be indexed in the core `core0`. If your entities/document have different languages then you can setup -a callback method, which returns the preferred core for the entity. - - /** - * @Solr\Document(indexHandler="indexHandler") - */ - class SomeEntity - { - public function indexHandler() - { - if ($this->language == 'en') { - return 'core0'; - } - } - } - -Each core must setup up in the config.yml under `endpoints`. If you leave the `index` or `indexHandler` property empty, -then a default core will be used (first in the `endpoints` list). To index a document in all cores use `*` as index value: - - @Solr\Document(index="*") - -## Solr field configuration - -Solr comes with a set of predefined field-name/field-types mapping: - -- title (solr-type: general_text) -- text (solr-type: general_text) -- category (solr-type: general_text) -- content_type (solr-type: string) - -For details have a look into your schema.xml. - -So if you have an entity with a property "category", then you don't need a type-declaration in the annotation: + } +} +``` + +Each core must be set up in `config.yml` under `endpoints`. If you leave the `index` or `indexHandler` property empty, +then the default core will be used (first one in the `endpoints` list). To index a document in all cores, use `*` as index value. + +## `@Solr\Id` annotation + +This annotation is required to index an entity. The annotation has no properties. You should add this annotation to the field that will be +used as the primary identifier for the entity/document. +```php +class Post +{ /** - * @Solr\Field - * @ORM\Column(name="category", type="text") + * @Solr\Id + * + * @ORM\Column(name="id", type="integer") + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + */ + + private $id; +} +``` + +### `generateId` option + +Set this option to true and a the bundle will generate a Id for you. Use this option if you have no underlying DB which + generates incremental Ids for you. + +## `@Solr\Field` annotation + +This annotation should be added to properties that should be indexed. You should specify the `type` option for the annotation. + +### `type` property + +Currently, a basic set of types is implemented: + +- string(s) +- text(s) +- date(s) +- integer(s) +- float(s) +- double(s) +- long(s) +- boolean(s) + +If you have a customized `schema.xml` than you don't need to setup a field-type. + +### `fieldModifier` property + +Solr supports partial updates of fields in an existing document. Supported values are: + +- set +- add (multivalue field only, adds a value(s) to a existing list) +- remove (multivalue field only, removes a value(s) from existing list) +- inc (integer field only) + +### `nestedClass` property + +Set this property if you want to index collections with nested Objects. + + + +### Object relations + +[For more information read the more detailed "How to index relation" guide](Resources/doc/index_relations.md) + +### `@Solr\SynchronizationFilter(callback="shouldBeIndexed")` annotation + +In some cases, an entity should not be indexed. For this, you have the `SynchronizationFilter` annotation to run a filter-callback. + +```php +/** + * // .... + * @Solr\SynchronizationFilter(callback="shouldBeIndexed") + */ +class SomeEntity +{ + /** + * @return boolean */ - private $category = ''; + public function shouldBeIndexed() + { + // put your logic here + } +} +``` + +The callback property specifies an callable function, which should return a boolean value, specifying whether a concrete +entity should be indexed. + +## Queries + +### Query a field of a document -The field has in this case automaticaly the type "general_text". +Querying the index is done via the `solr.client` service: -If you persist this entity, it will put automaticlly to the index. Update and delete happens automatically too. +```php +$query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); +$query->addSearchTerm('title', 'my title'); +$query->addSearchTerm('collection_field', array('value1', 'value2')); -## Query a field of a document +$result = $query->getResult(); +``` -To query the index you have to call some services. +or - $query = $this->get('solr')->createQuery('AcmeDemoBundle:Post'); - $query->addSearchTerm('title', 'my title'); +```php +$posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOneBy(array( + 'title' => 'my title', + 'collection_field' => array('value1', 'value2') +)); +``` - $result = $result = $query->getResult(); - -The $result array contains all found entities. The solr-service does all mappings from SolrDocument -to your entity for you. +### Query all fields of a document -## Query all fields of a document +The previous examples were only querying the `title` field. You can also query all fields with a string. -The pervious examples have queried only the field 'title'. You can also query all fields with a string. +```php +$query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); +$query->queryAllFields('my title'); - $query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); - $query->queryAllFields('my title'); +$result = $query->getResult(); +``` - $result = $query->getResult(); +### Define a custom query string +If you need more flexiblity in your queries you can define your own query strings: -## Define Result-Mapping +```php +$query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); +$query->setCustomQuery('id:post_* AND (author_s:Name1 OR author_s:Name2)'); -To narrow the mapping, you can use the `addField()` method. +$result = $query->getResult(); +``` - $query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); - $query->addSearchTerm('title', 'my title'); - $query->addField('id'); - $query->addField('text'); +### The QueryBuilder - $result = $query->getResult(); +The query-builder based on [https://github.com/minimalcode-org/search](/minimalcode-org/search) Criteria API. -In this case only the fields id and text will be mapped (addField()), so title and created_at will be -empty. If nothing was found $result is empty. +```php +$queryBuilder = $this->get('solr.client')->getQueryBuilder('AcmeDemoBundle:Post'); +$result = $queryBuilder + ->where('author') + ->is('Name1') + ->orWhere('author') + ->is('Name2') + ->getQuery() + ->getResult(); -## Configure HydrationModes +``` -HydrationMode tells the Bundle how to create an entity from a document. +To keep your code clean you should move the select-criteria in a repository-class: + +```php + +class YourRepository extends Repository +{ + public function findAuthor($name1, $name2) + { + return $this->getQueryBuilder() + ->where('author') + ->is($name1) + ->orWhere('author') + ->is($name2) + ->getQuery() + ->getResult(); + } +} + +``` + +### Configure HydrationModes + +HydrationMode tells the bundle how to create an entity from a document. 1. `FS\SolrBundle\Doctrine\Hydration\HydrationModes::HYDRATE_INDEX` - use only the data from solr 2. `FS\SolrBundle\Doctrine\Hydration\HydrationModes::HYDRATE_DOCTRINE` - merge the data from solr with the entire doctrine-entity With a custom query: - $query = $this->get('solr')->createQuery('AcmeDemoBundle:Post'); - $query->setHydrationMode($mode) +```php +$query = $this->get('solr.client')->createQuery('AcmeDemoBundle:Post'); +$query->setHydrationMode($mode) +``` With a custom document-repository you have to set the property `$hydrationMode` itself: - public function find($id) +```php +public function find($id) +{ + $this->hydrationMode = HydrationModes::HYDRATE_INDEX; + + return parent::find($id); +} +``` + +## Repositories + +Your should define your own repository-class to make your custom queries reuseable. How to configure a repository for a document have a look at [the annotation section](https://github.com/floriansemm/SolrBundle#setting-custom-repository-class-with-repository-option) + +```php +namespace AppBundle\Search; + +use FS\SolrBundle\Repository\Repository; + +class ProviderRepository extends Repository +{ + public function findPost($what) { - $this->hydrationMode = HydrationModes::HYDRATE_INDEX; + $query = $this->solr->createQuery('AcmeDemoBundle:Post'); + // some query-magic here - return parent::find($id); + return $query->getResult(); } +} +``` -## Index manually an entity +In your repository you have full access to the querybuilder. -To index your entities manually, you can do it the following way: +## Commands - $this->get('solr')->addDocument($entity); - $this->get('solr')->updateDocument($entity); - $this->get('solr')->deleteDocument($entity); +Here's all the commands provided by this bundle: -`deleteDocument()` requires that the entity-id is set. +* `solr:index:clear` - delete all documents in the index +* `solr:index:populate` - synchronize the db with the index +* `solr:schema:show` - shows your configured documents -## Use document repositories +### Indexing huge sets of entities -If you specify your own repository you must extend the `FS\SolrBundle\Repository\Repository` class. The useage is the same -like Doctrine-Repositories: +The `solr:index:populate` command works well for sets up to 300k entities, everthing large makes the command very slow. You can find [here](Resources/doc/indexing.md) some solution how to sync your DB with Solr. - $myRepository = $this->get('solr')->getRepository('AcmeDemoBundle:Post'); - $result = $myRepository->mySpecialFindMethod(); - -If you haven't declared a concrete repository in your entity and you calling `$this->get('solr')->getRepository('AcmeDemoBundle:Post')`, you will -get an instance of `FS\SolrBundle\Repository\Repository`. +## Extend Solarium -## Commands +To extend Solarium with your own plugins, create a tagged service: -There are comming two commands with this bundle: +```xml + +``` -* `solr:index:clear` - delete all documents in the index -* `solr:synchronize` - synchronize the db with the index +To hook into the [Solarium events](http://solarium.readthedocs.io/en/stable/customizing-solarium/#plugin-system) create a common Symfony event-listener: + +```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/Repository/Repository.php b/Repository/Repository.php index 68b1d1e6..35d810ce 100644 --- a/Repository/Repository.php +++ b/Repository/Repository.php @@ -2,10 +2,16 @@ namespace FS\SolrBundle\Repository; use FS\SolrBundle\Doctrine\Hydration\HydrationModes; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; use FS\SolrBundle\Query\FindByDocumentNameQuery; use FS\SolrBundle\Query\FindByIdentifierQuery; +use FS\SolrBundle\Query\QueryBuilderInterface; use FS\SolrBundle\Solr; +use FS\SolrBundle\SolrInterface; +/** + * Common repository class to find documents in the index + */ class Repository implements RepositoryInterface { @@ -15,9 +21,9 @@ class Repository implements RepositoryInterface protected $solr = null; /** - * @var object + * @var MetaInformationInterface */ - protected $entity = null; + protected $metaInformation = null; /** * @var string @@ -25,32 +31,28 @@ class Repository implements RepositoryInterface protected $hydrationMode = ''; /** - * @param Solr $solr - * @param object $entity + * @param SolrInterface $solr + * @param MetaInformationInterface $metaInformation */ - public function __construct(Solr $solr, $entity) + public function __construct(SolrInterface $solr, MetaInformationInterface $metaInformation) { $this->solr = $solr; - $this->entity = $entity; + $this->metaInformation = $metaInformation; $this->hydrationMode = HydrationModes::HYDRATE_DOCTRINE; } /** - * @param int $id - * @return object|null + * {@inheritdoc} */ public function find($id) { - $mapper = $this->solr->getMapper(); - $mapper->setMappingCommand($this->solr->getCommandFactory()->get('all')); - $metaInformation = $this->solr->getMetaFactory()->loadInformation($this->entity); - - $document = $mapper->toDocument($metaInformation); + $documentKey = $this->metaInformation->getDocumentName() . '_' . $id; $query = new FindByIdentifierQuery(); - $query->setDocument($document); - $query->setEntity($this->entity); + $query->setIndex($this->metaInformation->getIndex()); + $query->setDocumentKey($documentKey); + $query->setEntity($this->metaInformation->getEntity()); $query->setSolr($this->solr); $query->setHydrationMode($this->hydrationMode); $found = $this->solr->query($query); @@ -63,25 +65,15 @@ public function find($id) } /** - * @return array of found documents + * {@inheritdoc} */ public function findAll() { - $mapper = $this->solr->getMapper(); - $mapper->setMappingCommand($this->solr->getCommandFactory()->get('all')); - $metaInformation = $this->solr->getMetaFactory()->loadInformation($this->entity); - - $document = $mapper->toDocument($metaInformation); - - if (null === $document) { - return null; - } - - $document->removeField('id'); - $query = new FindByDocumentNameQuery(); - $query->setDocument($document); - $query->setEntity($this->entity); + $query->setRows(1000000); + $query->setDocumentName($this->metaInformation->getDocumentName()); + $query->setIndex($this->metaInformation->getIndex()); + $query->setEntity($this->metaInformation->getEntity()); $query->setSolr($this->solr); $query->setHydrationMode($this->hydrationMode); @@ -89,14 +81,21 @@ public function findAll() } /** - * @param array $args - * @return array of found documents + * {@inheritdoc} */ public function findBy(array $args) { - $query = $this->solr->createQuery($this->entity); + $query = $this->solr->createQuery($this->metaInformation->getEntity()); + $query->setHydrationMode($this->hydrationMode); + $query->setRows(100000); + $query->setUseAndOperator(true); + $query->addSearchTerm('id', $this->metaInformation->getDocumentName() . '_*'); + $query->setQueryDefaultField('id'); + $helper = $query->getHelper(); foreach ($args as $fieldName => $fieldValue) { + $fieldValue = $helper->escapeTerm($fieldValue); + $query->addSearchTerm($fieldName, $fieldValue); } @@ -104,13 +103,34 @@ public function findBy(array $args) } /** - * @param array $args - * @return array + * {@inheritdoc} */ public function findOneBy(array $args) { - $found = $this->findBy($args); + $query = $this->solr->createQuery($this->metaInformation->getEntity()); + $query->setHydrationMode($this->hydrationMode); + $query->setRows(1); + $query->setUseAndOperator(true); + $query->addSearchTerm('id', $this->metaInformation->getDocumentName() . '_*'); + $query->setQueryDefaultField('id'); + + $helper = $query->getHelper(); + foreach ($args as $fieldName => $fieldValue) { + $fieldValue = $helper->escapeTerm($fieldValue); + + $query->addSearchTerm($fieldName, $fieldValue); + } + + $found = $this->solr->query($query); return array_pop($found); } + + /** + * @return QueryBuilderInterface + */ + public function getQueryBuilder() + { + return $this->solr->getQueryBuilder($this->metaInformation->getEntity()); + } } diff --git a/Repository/RepositoryInterface.php b/Repository/RepositoryInterface.php index fa1d135a..a53011f3 100644 --- a/Repository/RepositoryInterface.php +++ b/Repository/RepositoryInterface.php @@ -1,23 +1,30 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - FS\SolrBundle\Doctrine\ORM\Listener\AddDocumentListener - FS\SolrBundle\Doctrine\ORM\Listener\DeleteDocumentListener - FS\SolrBundle\Doctrine\ORM\Listener\UpdateDocumentListener - - FS\SolrBundle\Doctrine\ODM\Listener\AddDocumentListener - FS\SolrBundle\Doctrine\ODM\Listener\DeleteDocumentListener - FS\SolrBundle\Doctrine\ODM\Listener\UpdateDocumentListener - + + + + + - - - - - - - - - + + - - - + + + + - - - + - - - - - - - - - + \ No newline at end of file diff --git a/Resources/config/log_listener.xml b/Resources/config/log_listener.xml index 82c16513..0e57fc87 100644 --- a/Resources/config/log_listener.xml +++ b/Resources/config/log_listener.xml @@ -1,46 +1,51 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + FS\SolrBundle\Event\Listener\InsertLogListener FS\SolrBundle\Event\Listener\UpdateLogListener FS\SolrBundle\Event\Listener\DeleteLogListener - FS\SolrBundle\Event\Listener\ErrorLogListener + FS\SolrBundle\Event\Listener\ErrorLogListener FS\SolrBundle\Event\Listener\ClearIndexLogListener - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + - - - - - - - + + \ No newline at end of file diff --git a/Resources/config/services.xml b/Resources/config/services.xml index f21815e7..8904cf2e 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -1,94 +1,104 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - FS\SolrBundle\Solr - FS\SolrBundle\Client\SolrBuilder - Solarium\Client - - FS\SolrBundle\Doctrine\Mapper\Mapping\CommandFactory - FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory - - FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolver - FS\SolrBundle\Doctrine\ClassnameResolver\KnownNamespaceAliases - - FS\SolrBundle\Doctrine\Mapper\Mapping\MapAllFieldsCommand - FS\SolrBundle\Doctrine\Mapper\Mapping\MapIdentifierCommand - - FS\SolrBundle\Console\ConsoleCommandResults - FS\SolrBundle\Event\Listener\SynchronizationSummaryListener - FS\SolrBundle\Console\ConsoleResultFactory - - - - - - - + + + + + + - + + + - - + + + - + + - + + - + - + + - - + - - - - - - - - - + + + + + - + + - - - + - - - + + - - + + + - - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + diff --git a/Resources/doc/index_relations.md b/Resources/doc/index_relations.md new file mode 100644 index 00000000..8c432088 --- /dev/null +++ b/Resources/doc/index_relations.md @@ -0,0 +1,238 @@ +# Index OneToOne/ManyToOne relation + +Given you have the following entity with a ManyToOne relation to `Category`. + +```php +setTitle('post category #1'); + +$post = new Post(); +$post->setTitle('a post title'); +$post->setCategory($category); + +$em = $this->getDoctrine()->getManager(); +$em->persist($post); +$em->flush(); +``` + +### Quering the relation + +```php +$posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOneBy(array( + 'category' => 'post category #1' +)); +``` + +# 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 +setTitle($postTitle); +$post->setText('relation'); +$post->setTags(array( + new Tag('tag #1'), + new Tag('tag #2'), + new Tag('tag #3') +)); + +$em = $this->getDoctrine()->getManager(); +$em->persist($post); +$em->flush(); +``` + +Which will result in a document like this: + +```json +"docs": [ + { + "id": "post_391", + "title_s": "post 25.03.2016", + "text_t": "relation", + "tags_ss": [ + "tag #1", + "tag #2", + "tag #3" + ], + "_version_": 1529771282767282200 + } +] +``` + +### Quering the strings collection + +Now `Post` can be searched like this + +```php +$posts = $this->get('solr.client')->getRepository('AcmeDemoBundle:Post')->findOneBy(array( + 'tags' => 'tag #1' +)); +``` + +## 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' +)); +``` + diff --git a/Resources/doc/indexing.md b/Resources/doc/indexing.md new file mode 100644 index 00000000..2f5d0e9a --- /dev/null +++ b/Resources/doc/indexing.md @@ -0,0 +1,160 @@ +# How to index more than 500k entities + +If you want to index a lot of entities then it is not a good idea to use `solr:index:populate`. +The command works well with 100k-200k entites everything larger than that makes the command incredible slow and needs a lot of memory. This is because doctrine is not designed to handle hundred-thousands of entities. + +In my following example I have a `person` table with 5000000 rows and three columns: `id`, `name` and `email`. The resulting documents are schemaless, so all fields have a suffix e.g. `name_s`. + +Here are some possibilities which works well for me: + +## CSV export with MySQL Prepared Statement + Solr PostTool + +This solution does not use PHP. + +1. export your data to person.csv +```sql +SET @TS = DATE_FORMAT(NOW(),'_%Y_%m_%d_%H_%i_%s'); + +SET @FOLDER = '/tmp/'; -- target dir +SET @PREFIX = 'person'; +SET @EXT = '.csv'; + +-- first select defines the header of the csv-file +SET @CMD = CONCAT("SELECT 'id', 'name_s', 'email_s' UNION ALL SELECT * FROM person INTO OUTFILE '",@FOLDER,@PREFIX,@TS,@EXT, + "' FIELDS ENCLOSED BY '\"' TERMINATED BY ',' ESCAPED BY '\"'", + " LINES TERMINATED BY '\r\n';"); + +PREPARE statement FROM @CMD; + +EXECUTE statement; +``` + +Then run this SQL-script: + +```bash +mysql -udbuser -p123 dbname < dump_person_table.sql +``` + +The resulting file looks like this: `/tmp/person_2017_03_01_11_21_41.csv` + +2. index the csv with [post-tool](https://lucidworks.com/2015/08/04/solr-5-new-binpost-utility/) + +```bash +/opt/solr/solr-5.5.2/bin/post -c core0 /tmp/person_2017_03_01_11_21_41.csv +``` + +## PDO Select + [Solarium BufferedAdd](http://solarium.readthedocs.io/en/stable/plugins/#example-usage) + +The script has two parts: + +1. select a chunk of rows from the DB +2. add the rows to the index with Solarium + +```php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$statement = $connection->prepare('SELECT COUNT(*) as total_items FROM person'); +$statement->execute(); +$countResult = $statement->fetch(PDO::FETCH_ASSOC); + +$totalItems = $countResult['total_items']; +$batchSize = 5000; + +$pages = ceil($totalItems / $batchSize); + +$client = new Solarium\Client([ + 'endpoint' => [ + 'localhost' => [ + 'host' => 'localhost', + 'port' => 8983, + 'path' => '/solr/core0', + ] + ] +]); + +/** @var \Solarium\Plugin\BufferedAdd\BufferedAdd $buffer */ +$buffer = $client->getPlugin('bufferedadd'); +$buffer->setBufferSize($batchSize); + +for ($i = 0; $i <= $pages; $i++) { + $limitStart = ($i - 1) * $batchSize; + $limitEnd = $batchSize * $i; + if ($i == 0) { + $limitStart = 1; + $limitEnd = $batchSize; + } + + $statement = $connection->prepare(sprintf('SELECT id, name, email FROM person WHERE id >= %s AND id <= %s ', $limitStart, $limitEnd)); + $statement->execute(); + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $item) { + $buffer->createDocument([ + 'id' => $item['id'], + 'name_s' => $item['name'], + 'email_s' => $item['email'] + ]); + } + + $statement->closeCursor(); + + $buffer->commit(); + + echo sprintf('Indexing page %s / %s', $i, $pages) . PHP_EOL; +} + +$buffer->flush(); +``` + +# PDO Select + CSV Export + Solr Post-Tool + +This solution exports the database to csv by using PDO. The exported files are located under `/tmp/export`. + +```php +$connection = new PDO('mysql:host=localhost;dbname=dbname;charset=utf8mb4', 'dbuser', '123'); +$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$statement = $connection->prepare('SELECT COUNT(*) as total_items FROM person'); +$statement->execute(); +$countResult = $statement->fetch(PDO::FETCH_ASSOC); +$statement->closeCursor(); + +$totalItems = $countResult['total_items']; +$batchSize = 10000; + +$pages = ceil($totalItems / $batchSize); + +@mkdir('/tmp/export'); + +for ($i = 0; $i <= $pages; $i++) { + $data = []; + $limitStart = ($i - 1) * $batchSize; + $limitEnd = $batchSize * $i; + if ($i == 0) { + $limitStart = 1; + $limitEnd = $batchSize; + } + + $statement = $connection->prepare(sprintf('SELECT id, name, email FROM person WHERE id >= %s AND id <= %s ', $limitStart, $limitEnd)); + $statement->execute(); + + $data[] = "id, name_s, email_s\n"; + + foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $item) { + $data[] = sprintf("\"%s\", \"%s\", \"%s\"", $item['id'], $item['name'], $item['email']); + } + + $statement->closeCursor(); + + file_put_contents(sprintf('/tmp/export/person_%s.csv', $i), join("\n", $data)); + + echo sprintf('Indexing page %s / %s', $i, $pages) . PHP_EOL; +} +``` + +To import the data we are using Solr Post-Tool: + +`/opt/solr/solr-5.5.2/bin/post -c core0 /tmp/export` diff --git a/Resources/views/Profiler/icon.svg b/Resources/views/Profiler/icon.svg new file mode 100644 index 00000000..8b9214fa --- /dev/null +++ b/Resources/views/Profiler/icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Resources/views/Profiler/solr.html.twig b/Resources/views/Profiler/solr.html.twig new file mode 100644 index 00000000..7c2036b8 --- /dev/null +++ b/Resources/views/Profiler/solr.html.twig @@ -0,0 +1,222 @@ +{% extends app.request.isXmlHttpRequest ? '@WebProfiler/Profiler/ajax_layout.html.twig' : '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% if collector.querycount > 0 %} + {% if profiler_markup_version == 1 %} + {% 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' %} + + {% set icon %} + {{ include('@FSSolr/Profiler/icon.svg') }} + {{ collector.querycount }} + + in + {{ '%0.2f'|format(collector.time * 1000) }} + ms + + {% endset %} + + {% set text %} +
+ Solr queries + {{ collector.querycount }} +
+
+ Query time + {{ '%0.2f'|format(collector.time * 1000) }} ms +
+ {% endset %} + {% endif %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status|default('') }) }} + {% endif %} +{% endblock %} + +{% block menu %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {% if profiler_markup_version == 1 %} + + + Solr + Solr + + {{ collector.querycount }} + {{ '%0.0f'|format(collector.time * 1000) }} ms + + + + {% else %} + + + {{ include('@FSSolr/Profiler/icon.svg') }} + Solr + + + {% endif %} +{% endblock %} + +{% block panel %} + {% set profiler_markup_version = profiler_markup_version|default(1) %} + + {{ block('queries') }} +{% endblock %} + +{% block queries %} + {% if profiler_markup_version == 1 %} + + + +

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 %} +
+

No solr queries were performed.

+
+ {% else %} + + + + + + + + + + + + + {% for i, query in collector.queries %} + + + + + + + + + {% endfor %} + +
#TimeEnd PointMethodParametersRaw Data
{{ loop.index }}{{ '%0.2f'|format(query.executionMS * 1000) }} ms + {{ query.endpoint }} + + {{ query.method }} + + {% if query.stub is not defined %} + {{ query.params | replace({'&': "
"}) | raw }} + {% else %} + {{ profiler_dump(query.stub, 1) }} + {% endif %} +
+
{{ query.raw_data }}
+
+ {% endif %} + + +{% endblock %} diff --git a/Solr.php b/Solr.php index 72acc983..9c303a93 100644 --- a/Solr.php +++ b/Solr.php @@ -1,27 +1,36 @@ solrClientCore = $client; - $this->commandFactory = $commandFactory; $this->eventManager = $manager; $this->metaInformationFactory = $metaInformationFactory; $this->entityMapper = $entityMapper; } - + /** * @return Client */ - public function getClient() + public function getClient(): Client { return $this->solrClientCore; } @@ -85,104 +87,114 @@ public function getClient() /** * @return EntityMapper */ - public function getMapper() + public function getMapper(): EntityMapper { return $this->entityMapper; } /** - * @return CommandFactory + * @return MetaInformationFactory */ - public function getCommandFactory() + public function getMetaFactory(): MetaInformationFactory { - return $this->commandFactory; + return $this->metaInformationFactory; } /** - * @return MetaInformationFactory + * @return DocumentHelper */ - public function getMetaFactory() + public function getDocumentHelper() { - return $this->metaInformationFactory; + return new DocumentHelper($this); } /** - * @param object $entity + * @param object|string $entity entity, entity-alias or classname * * @return SolrQuery */ - public function createQuery($entity) + public function createQuery($entity): SolrQuery { $metaInformation = $this->metaInformationFactory->loadInformation($entity); - $class = $metaInformation->getClassName(); - $entity = new $class; $query = new SolrQuery(); $query->setSolr($this); - $query->setEntity($entity); - + $query->setEntity($metaInformation->getClassName()); + $query->setIndex($metaInformation->getIndex()); + $query->setMetaInformation($metaInformation); $query->setMappedFields($metaInformation->getFieldMapping()); return $query; } /** - * @param string $entityAlias - * - * @return Repository + * @param string|object $entity * - * @throws \RuntimeException if repository does not extend FS\SolrBundle\Repository\Repository + * @return QueryBuilderInterface */ - public function getRepository($entityAlias) + public function getQueryBuilder($entity): QueryBuilderInterface { - $metaInformation = $this->metaInformationFactory->loadInformation($entityAlias); - $class = $metaInformation->getClassName(); + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + return new QueryBuilder($this, $metaInformation); + } - $entity = new $class; + /** + * {@inheritdoc} + */ + public function getRepository($entity): RepositoryInterface + { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); $repositoryClass = $metaInformation->getRepository(); if (class_exists($repositoryClass)) { - $repositoryInstance = new $repositoryClass($this, $entity); + $repositoryInstance = new $repositoryClass($this, $metaInformation); if ($repositoryInstance instanceof Repository) { return $repositoryInstance; } - throw new \RuntimeException(sprintf( - '%s must extends the FS\SolrBundle\Repository\Repository', - $repositoryClass - )); + throw new SolrException(sprintf('%s must extends the FS\SolrBundle\Repository\Repository', $repositoryClass)); } - return new Repository($this, $entity); + return new Repository($this, $metaInformation); } /** - * @param object $entity + * {@inheritdoc} */ - public function removeDocument($entity) + public function createQueryBuilder($entity): QueryBuilderInterface { - $command = $this->commandFactory->get('identifier'); + $metaInformation = $this->metaInformationFactory->loadInformation($entity); - $this->entityMapper->setMappingCommand($command); + return new QueryBuilder($this, $metaInformation); + } - $metaInformations = $this->metaInformationFactory->loadInformation($entity); + /** + * {@inheritdoc} + */ + public function removeDocument($entity) + { + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $event = new Event($this->solrClientCore, $metaInformation); + $this->eventManager->dispatch(Events::PRE_DELETE, $event); - if ($document = $this->entityMapper->toDocument($metaInformations)) { - $event = new Event($this->solrClientCore, $metaInformations); - $this->eventManager->dispatch(Events::PRE_DELETE, $event); + if ($document = $this->entityMapper->toDocument($metaInformation)) { try { - $indexName = $metaInformations->getIndex(); + $indexName = $metaInformation->getIndex(); - $client = new \FS\SolrBundle\Client\Client($this->solrClientCore); + $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); + + throw new SolrException($e->getMessage(), $e->getCode(), $e); } $this->eventManager->dispatch(Events::POST_DELETE, $event); @@ -190,35 +202,41 @@ public function removeDocument($entity) } /** - * @param object $entity + * {@inheritdoc} */ - public function addDocument($entity) + public function addDocument($entity): bool { $metaInformation = $this->metaInformationFactory->loadInformation($entity); if (!$this->addToIndex($metaInformation, $entity)) { - return; + return false; } - $doc = $this->toDocument($metaInformation); + if ($metaInformation->isNested()) { + return false; + } $event = new Event($this->solrClientCore, $metaInformation); $this->eventManager->dispatch(Events::PRE_INSERT, $event); + $doc = $this->toDocument($metaInformation); + $this->addDocumentToIndex($doc, $metaInformation, $event); $this->eventManager->dispatch(Events::POST_INSERT, $event); + + return true; } /** - * @param MetaInformation $metaInformation - * @param object $entity + * @param MetaInformationInterface $metaInformation + * @param object $entity * * @return boolean * - * @throws \BadMethodCallException if callback method not exists + * @throws SolrException if callback method not exists */ - private function addToIndex(MetaInformation $metaInformation, $entity) + private function addToIndex(MetaInformationInterface $metaInformation, $entity): bool { if (!$metaInformation->hasSynchronizationFilter()) { return true; @@ -226,102 +244,145 @@ private function addToIndex(MetaInformation $metaInformation, $entity) $callback = $metaInformation->getSynchronizationCallback(); if (!method_exists($entity, $callback)) { - throw new \BadMethodCallException(sprintf('unknown method %s in entity %s', $callback, get_class($entity))); + throw new SolrException(sprintf('unknown method %s in entity %s', $callback, get_class($entity))); } return $entity->$callback(); } /** + * Get select query + * * @param AbstractQuery $query * - * @return array of found documents + * @return SolariumQuery */ - public function query(AbstractQuery $query) + public function getSelectQuery(AbstractQuery $query): SolariumQuery { - $entity = $query->getEntity(); + $selectQuery = $this->solrClientCore->createSelect($query->getOptions()); + + $selectQuery->setQuery($query->getQuery()); + $selectQuery->setFilterQueries($query->getFilterQueries()); + $selectQuery->setSorts($query->getSorts()); + $selectQuery->setFields($query->getFields()); + + return $selectQuery; + } - $queryString = $query->getQuery(); - $query = $this->solrClientCore->createSelect($query->getOptions()); - $query->setQuery($queryString); + /** + * {@inheritdoc} + */ + public function query(AbstractQuery $query): array + { + $entity = $query->getEntity(); + $runQueryInIndex = $query->getIndex(); + $selectQuery = $this->getSelectQuery($query); try { - $response = $this->solrClientCore->select($query); + $response = $this->solrClientCore->select($selectQuery, $runQueryInIndex); + + $this->numberOfFoundDocuments = $response->getNumFound(); + + $entities = array(); + foreach ($response as $document) { + $entities[] = $this->entityMapper->toEntity($document, $entity); + } + + return $entities; } catch (\Exception $e) { $errorEvent = new ErrorEvent(null, null, 'query solr'); $errorEvent->setException($e); $this->eventManager->dispatch(Events::ERROR, $errorEvent); - return array(); - } - - $this->numberOfFoundDocuments = $response->getNumFound(); - if ($this->numberOfFoundDocuments == 0) { - return array(); + throw new SolrException($e->getMessage(), $e->getCode(), $e); } - - $targetEntity = $entity; - $mappedEntities = array(); - foreach ($response as $document) { - $mappedEntities[] = $this->entityMapper->toEntity($document, $targetEntity); - } - - return $mappedEntities; } /** - * Number of results found by query + * Number of overall found documents for a given 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() { $this->eventManager->dispatch(Events::PRE_CLEAR_INDEX, new Event($this->solrClientCore)); try { - $client = new \FS\SolrBundle\Client\Client($this->solrClientCore); + $client = new SolariumMulticoreClient($this->solrClientCore); $client->clearCores(); } catch (\Exception $e) { $errorEvent = new ErrorEvent(null, null, 'clear-index'); $errorEvent->setException($e); $this->eventManager->dispatch(Events::ERROR, $errorEvent); + + throw new SolrException($e->getMessage(), $e->getCode(), $e); } $this->eventManager->dispatch(Events::POST_CLEAR_INDEX, new Event($this->solrClientCore)); } /** - * @param object $entity + * @param array $entities */ - public function synchronizeIndex($entity) + public function synchronizeIndex($entities) { - $this->updateDocument($entity); + /** @var BufferedAdd $buffer */ + $buffer = $this->solrClientCore->getPlugin('bufferedadd'); + $buffer->setBufferSize(500); + + $allDocuments = array(); + foreach ($entities as $entity) { + $metaInformations = $this->metaInformationFactory->loadInformation($entity); + + if (!$this->addToIndex($metaInformations, $entity)) { + continue; + } + + $doc = $this->toDocument($metaInformations); + + $allDocuments[$metaInformations->getIndex()][] = $doc; + } + + foreach ($allDocuments as $core => $documents) { + $buffer->addDocuments($documents); + + if ($core == '') { + $core = null; + } + $buffer->setEndpoint($core); + + $buffer->commit(); + } } /** - * @param object $entity - * - * @return bool + * {@inheritdoc} */ - public function updateDocument($entity) + public function updateDocument($entity): bool { $metaInformations = $this->metaInformationFactory->loadInformation($entity); - $doc = $this->toDocument($metaInformations); + if (!$this->addToIndex($metaInformations, $entity)) { + return false; + } $event = new Event($this->solrClientCore, $metaInformations); $this->eventManager->dispatch(Events::PRE_UPDATE, $event); + $doc = $this->toDocument($metaInformations); + $this->addDocumentToIndex($doc, $metaInformations, $event); $this->eventManager->dispatch(Events::POST_UPDATE, $event); @@ -330,31 +391,30 @@ public function updateDocument($entity) } /** - * @param MetaInformation $metaInformation + * @param MetaInformationInterface $metaInformation * - * @return Document + * @return DocumentInterface */ - private function toDocument(MetaInformation $metaInformation) + private function toDocument(MetaInformationInterface $metaInformation): DocumentInterface { - $command = $this->commandFactory->get('all'); - - $this->entityMapper->setMappingCommand($command); $doc = $this->entityMapper->toDocument($metaInformation); return $doc; } /** - * @param object $doc - * @param MetaInformation $metaInformation - * @param Event $event + * @param object $doc + * @param MetaInformationInterface $metaInformation + * @param Event $event + * + * @throws SolrException if an error occurs */ - private function addDocumentToIndex($doc, MetaInformation $metaInformation, Event $event) + private function addDocumentToIndex($doc, MetaInformationInterface $metaInformation, Event $event) { try { $indexName = $metaInformation->getIndex(); - $client = new \FS\SolrBundle\Client\Client($this->solrClientCore); + $client = new SolariumMulticoreClient($this->solrClientCore); $client->update($doc, $indexName); } catch (\Exception $e) { @@ -362,6 +422,8 @@ private function addDocumentToIndex($doc, MetaInformation $metaInformation, Even $errorEvent->setException($e); $this->eventManager->dispatch(Events::ERROR, $errorEvent); + + throw new SolrException($e->getMessage(), $e->getCode(), $e); } } } diff --git a/SolrException.php b/SolrException.php new file mode 100644 index 00000000..61d8543e --- /dev/null +++ b/SolrException.php @@ -0,0 +1,8 @@ +metaFactory = new MetaInformationFactory(new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->mapper = $this->getMockBuilder(EntityMapperInterface::class) + ->disableOriginalConstructor() + ->setMethods(array('setMappingCommand', 'toDocument', 'toEntity', 'setHydrationMode')) + ->getMock(); + + $this->solrClientFake = $this->createMock(Client::class); + + $this->solr = new Solr($this->solrClientFake, $this->eventDispatcher, $this->metaFactory, $this->mapper); + } + + protected function assertUpdateQueryExecuted($index = null) + { + $updateQuery = $this->createMock(UpdateQuery::class); + $updateQuery->expects($this->once()) + ->method('addDocument'); + + $updateQuery->expects($this->once()) + ->method('addCommit'); + + $this->solrClientFake + ->expects($this->once()) + ->method('createUpdate') + ->will($this->returnValue($updateQuery)); + + $this->solrClientFake + ->expects($this->once()) + ->method('update') + ->with($updateQuery, $index); + + return $updateQuery; + } + + protected function assertUpdateQueryWasNotExecuted() + { + $updateQuery = $this->createMock(UpdateQuery::class); + $updateQuery->expects($this->never()) + ->method('addDocument'); + + $updateQuery->expects($this->never()) + ->method('addCommit'); + + $this->solrClientFake + ->expects($this->never()) + ->method('createUpdate'); + } + + protected function assertDeleteQueryWasExecuted() + { + $deleteQuery = $this->createMock(UpdateQuery::class); + $deleteQuery->expects($this->once()) + ->method('addDeleteQuery') + ->with($this->isType('string')); + + $deleteQuery->expects($this->once()) + ->method('addCommit'); + + $this->solrClientFake + ->expects($this->once()) + ->method('createUpdate') + ->will($this->returnValue($deleteQuery)); + + $this->solrClientFake + ->expects($this->once()) + ->method('update') + ->with($deleteQuery); + } + + protected function setupMetaFactoryLoadOneCompleteInformation($metaInformation = null) + { + if (null === $metaInformation) { + $metaInformation = MetaTestInformationFactory::getMetaInformation(); + } + + $this->metaFactory->expects($this->once()) + ->method('loadInformation') + ->will($this->returnValue($metaInformation)); + } + + protected function assertQueryWasExecuted($data = array(), $index) + { + $selectQuery = $this->createMock(SelectQuery::class); + $selectQuery->expects($this->once()) + ->method('setQuery'); + + $queryResult = new ResultFake($data); + + $this->solrClientFake + ->expects($this->once()) + ->method('createSelect') + ->will($this->returnValue($selectQuery)); + + $this->solrClientFake + ->expects($this->once()) + ->method('select') + ->will($this->returnValue($queryResult)); + } + + protected function mapOneDocument() + { + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue($this->createMock(DocumentInterface::class))); + } +} \ No newline at end of file diff --git a/Tests/Client/Solarium/SolariumClientBuilderTest.php b/Tests/Client/Solarium/SolariumClientBuilderTest.php new file mode 100644 index 00000000..8a9723c0 --- /dev/null +++ b/Tests/Client/Solarium/SolariumClientBuilderTest.php @@ -0,0 +1,137 @@ +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 testCreateClientWithDsn($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 testCreateClientWithDsnAndCore($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'); + } + + /** + * @return array + */ + 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(); + } +} diff --git a/Tests/Console/ConsoleCommandResultsTest.php b/Tests/Console/ConsoleCommandResultsTest.php deleted file mode 100644 index c33697ed..00000000 --- a/Tests/Console/ConsoleCommandResultsTest.php +++ /dev/null @@ -1,33 +0,0 @@ -success(new CommandResult(1, 'a class')); - $results->success(new CommandResult(2, 'a class')); - $results->success(new CommandResult(3, 'a class')); - - $results->error(new CommandResult(2, 'a class')); - - $this->assertEquals(2, $results->getSucceed()); - - $this->assertArrayHasKey(2, $results->getErrors()); - - $this->assertArrayHasKey(1, $results->getSuccess()); - $this->assertArrayHasKey(3, $results->getSuccess()); - $this->assertArrayNotHasKey(2, $results->getSuccess()); - } -} - \ No newline at end of file diff --git a/Tests/Console/ConsoleResultFactoryTest.php b/Tests/Console/ConsoleResultFactoryTest.php deleted file mode 100644 index 55e464df..00000000 --- a/Tests/Console/ConsoleResultFactoryTest.php +++ /dev/null @@ -1,66 +0,0 @@ -setException(new \Exception('message')); - - $factory = new ConsoleResultFactory(); - $result = $factory->fromEvent($error); - - $this->assertEquals('message', $result->getMessage()); - } - - /** - * @test - */ - public function resultNotContainsIdAndEntityWhenMetaInformationNull() - { - $event = new Event(null, null, ''); - - $factory = new ConsoleResultFactory(); - $result = $factory->fromEvent($event); - - $this->assertEquals(null, $result->getResultId()); - $this->assertEquals('', $result->getEntity()); - $this->assertEquals('', $result->getMessage()); - } - - /** - * @test - */ - public function resultFromSuccessEventContainsNoMessage() - { - $entity = new ValidTestEntity(); - $entity->setId(1); - - $metaInformation = new MetaInformation(); - $metaInformation->setClassName('an entity'); - $metaInformation->setEntity($entity); - - $event = new Event(null, $metaInformation, ''); - - $factory = new ConsoleResultFactory(); - $result = $factory->fromEvent($event); - - $this->assertEquals(1, $result->getResultId()); - $this->assertEquals('an entity', $result->getEntity()); - $this->assertEquals('', $result->getMessage()); - } -} - \ No newline at end of file diff --git a/Tests/DependencyInjection/FSSolrExtensionTest.php b/Tests/DependencyInjection/FSSolrExtensionTest.php index 5baf0e5d..d83a755a 100644 --- a/Tests/DependencyInjection/FSSolrExtensionTest.php +++ b/Tests/DependencyInjection/FSSolrExtensionTest.php @@ -26,12 +26,12 @@ public function setUp() private function enableOdmConfig() { - $this->container->setParameter('doctrine_mongodb.odm.document_managers', array('default'=>'odm.default.mananger')); + $this->container->setParameter('doctrine_mongodb.odm.document_managers', array('default' => 'odm.default.mananger')); } private function enableOrmConfig() { - $this->container->setParameter('doctrine.entity_managers', array('default'=>'orm.default.mananger')); + $this->container->setParameter('doctrine.entity_managers', array('default' => 'orm.default.mananger')); } private function commonConfig() @@ -56,13 +56,9 @@ public function testDoctrineORMSetup() $extension = new FSSolrExtension(); $extension->load($config, $this->container); - $this->assertTrue($this->container->has('solr.update.document.orm.listener'), 'update listener'); - $this->assertTrue($this->container->has('solr.delete.document.orm.listener'), 'delete listener'); - $this->assertTrue($this->container->has('solr.add.document.orm.listener'), 'insert listener'); + $this->assertTrue($this->container->has('solr.document.orm.subscriber'), 'orm subscriber'); - $this->assertDefinitionHasTag('solr.update.document.orm.listener', 'doctrine.event_listener'); - $this->assertDefinitionHasTag('solr.delete.document.orm.listener', 'doctrine.event_listener'); - $this->assertDefinitionHasTag('solr.add.document.orm.listener', 'doctrine.event_listener'); + $this->assertDefinitionHasTag('solr.document.orm.subscriber', 'doctrine.event_subscriber'); $this->assertClassnameResolverHasOrmDefaultConfiguration(); } @@ -75,13 +71,9 @@ public function testDoctrineODMSetup() $extension = new FSSolrExtension(); $extension->load($config, $this->container); - $this->assertTrue($this->container->has('solr.update.document.odm.listener'), 'update listener'); - $this->assertTrue($this->container->has('solr.delete.document.odm.listener'), 'delete listener'); - $this->assertTrue($this->container->has('solr.add.document.odm.listener'), 'insert listener'); + $this->assertTrue($this->container->has('solr.document.odm.subscriber'), 'odm subscriber'); - $this->assertDefinitionHasTag('solr.update.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); - $this->assertDefinitionHasTag('solr.delete.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); - $this->assertDefinitionHasTag('solr.add.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); + $this->assertDefinitionHasTag('solr.document.odm.subscriber', 'doctrine_mongodb.odm.event_subscriber'); $this->assertClassnameResolverHasOdmDefaultConfiguration(); } @@ -98,21 +90,11 @@ public function solrListensToOdmAndOrmEvents() $extension = new FSSolrExtension(); $extension->load($config, $this->container); - $this->assertTrue($this->container->has('solr.update.document.odm.listener'), 'update listener'); - $this->assertTrue($this->container->has('solr.delete.document.odm.listener'), 'delete listener'); - $this->assertTrue($this->container->has('solr.add.document.odm.listener'), 'insert listener'); + $this->assertTrue($this->container->has('solr.document.odm.subscriber'), 'odm subscriber'); + $this->assertDefinitionHasTag('solr.document.odm.subscriber', 'doctrine_mongodb.odm.event_subscriber'); - $this->assertDefinitionHasTag('solr.update.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); - $this->assertDefinitionHasTag('solr.delete.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); - $this->assertDefinitionHasTag('solr.add.document.odm.listener', 'doctrine_mongodb.odm.event_listener'); - - $this->assertTrue($this->container->has('solr.update.document.orm.listener'), 'update listener'); - $this->assertTrue($this->container->has('solr.delete.document.orm.listener'), 'delete listener'); - $this->assertTrue($this->container->has('solr.add.document.orm.listener'), 'insert listener'); - - $this->assertDefinitionHasTag('solr.update.document.orm.listener', 'doctrine.event_listener'); - $this->assertDefinitionHasTag('solr.delete.document.orm.listener', 'doctrine.event_listener'); - $this->assertDefinitionHasTag('solr.add.document.orm.listener', 'doctrine.event_listener'); + $this->assertTrue($this->container->has('solr.document.orm.subscriber'), 'orm subscriber'); + $this->assertDefinitionHasTag('solr.document.orm.subscriber', 'doctrine.event_subscriber'); } private function assertClassnameResolverHasOrmDefaultConfiguration() diff --git a/Tests/Doctrine/Annotation/AnnotationReaderTest.php b/Tests/Doctrine/Annotation/AnnotationReaderTest.php index d32b41d6..508a4677 100644 --- a/Tests/Doctrine/Annotation/AnnotationReaderTest.php +++ b/Tests/Doctrine/Annotation/AnnotationReaderTest.php @@ -2,19 +2,22 @@ namespace FS\SolrBundle\Tests\Doctrine\Mapping\Mapper; +use Doctrine\Common\Annotations\Reader; use FS\SolrBundle\Doctrine\Annotation\Field; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityIndexHandler; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityIndexProperty; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityNoBoost; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityNoTypes; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityFiltered; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityFloatBoost; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityNumericFields; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityWithInvalidBoost; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidEntityRepository; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityIndexHandler; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityIndexProperty; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityNoBoost; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityNoTypes; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityFiltered; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityFloatBoost; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityNumericFields; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithInvalidBoost; +use FS\SolrBundle\Tests\Fixtures\ValidOdmTestDocument; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\EntityWithRepository; -use FS\SolrBundle\Tests\Doctrine\Mapper\NotIndexedEntity; +use FS\SolrBundle\Tests\Fixtures\EntityWithRepository; +use FS\SolrBundle\Tests\Fixtures\NotIndexedEntity; /** * @@ -22,30 +25,33 @@ */ class AnnotationReaderTest extends \PHPUnit_Framework_TestCase { + /** + * @var AnnotationReader + */ + private $reader; - public function testGetFields_NoFieldsDected() + public function setUp() { - $reader = new AnnotationReader(); + $this->reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader()); + } - $fields = $reader->getFields(new NotIndexedEntity()); + public function testGetFields_NoFieldsDected() + { + $fields = $this->reader->getFields(new NotIndexedEntity()); $this->assertEquals(0, count($fields)); } public function testGetFields_ThreeFieldsDetected() { - $reader = new AnnotationReader(); + $fields = $this->reader->getFields(new ValidTestEntity()); - $fields = $reader->getFields(new ValidTestEntity()); - - $this->assertEquals(4, count($fields), '4 fields are mapped'); + $this->assertEquals(5, count($fields), '5 fields are mapped'); } public function testGetFields_OneFieldsOneTypes() { - $reader = new AnnotationReader(); - - $fields = $reader->getFields(new ValidTestEntityNoTypes()); + $fields = $this->reader->getFields(new ValidTestEntityNoTypes()); $this->assertEquals(1, count($fields), '1 fields are mapped'); @@ -55,49 +61,41 @@ public function testGetFields_OneFieldsOneTypes() } /** - * @expectedException RuntimeException + * @expectedException \FS\SolrBundle\Doctrine\Annotation\AnnotationReaderException + * @expectedExceptionMessage no identifer declared in entity FS\SolrBundle\Tests\Fixtures\NotIndexedEntity */ public function testGetIdentifier_ShouldThrowException() { - $reader = new AnnotationReader(); - - $reader->getIdentifier(new NotIndexedEntity()); + $this->reader->getIdentifier(new NotIndexedEntity()); } public function testGetIdentifier() { - $reader = new AnnotationReader(); - - $id = $reader->getIdentifier(new ValidTestEntity()); + $id = $this->reader->getIdentifier(new ValidTestEntity()); $this->assertEquals('id', $id->name); + $this->assertFalse($id->generateId); } public function testGetFieldMapping_ThreeMappingsAndId() { - $reader = new AnnotationReader(); - - $fields = $reader->getFieldMapping(new ValidTestEntity()); + $fields = $this->reader->getFieldMapping(new ValidTestEntity()); - $this->assertEquals(5, count($fields), 'five fields are mapped'); + $this->assertEquals(6, count($fields), 'six fields are mapped'); $this->assertTrue(array_key_exists('title', $fields)); $this->assertTrue(array_key_exists('id', $fields)); } public function testGetRepository_ValidRepositoryDeclared() { - $reader = new AnnotationReader(); - $repository = $reader->getRepository(new EntityWithRepository()); + $repositoryClassname = $this->reader->getRepository(new EntityWithRepository()); - $expected = 'FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidEntityRepository'; - $actual = $repository; - $this->assertEquals($expected, $actual, 'wrong declared repository'); + $this->assertEquals(ValidEntityRepository::class, $repositoryClassname, 'wrong declared repository'); } public function testGetRepository_NoRepositoryAttributSet() { - $reader = new AnnotationReader(); - $repository = $reader->getRepository(new ValidTestEntity()); + $repository = $this->reader->getRepository(new ValidTestEntity()); $expected = ''; $actual = $repository; @@ -106,56 +104,44 @@ public function testGetRepository_NoRepositoryAttributSet() public function testGetBoost() { - $reader = new AnnotationReader(); - $boost = $reader->getEntityBoost(new ValidTestEntity()); + $boost = $this->reader->getEntityBoost(new ValidTestEntity()); $this->assertEquals(1, $boost); } + /** + * @expectedException \FS\SolrBundle\Doctrine\Annotation\AnnotationReaderException + * @expectedExceptionMessage Invalid boost value "aaaa" in class "FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithInvalidBoost" configured + */ public function testGetBoost_BoostNotNumeric() { - $reader = new AnnotationReader(); - - try { - $boost = $reader->getEntityBoost(new ValidTestEntityWithInvalidBoost()); - - $this->fail(); - } catch (\InvalidArgumentException $e) { - $this->assertEquals( - 'Invalid boost value aaaa for entity FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityWithInvalidBoost', - $e->getMessage() - ); - } + $this->reader->getEntityBoost(new ValidTestEntityWithInvalidBoost()); } public function testGetBoost_BoostIsNumberic() { - $reader = new AnnotationReader(); - $boost = $reader->getEntityBoost(new ValidTestEntityFloatBoost()); + $boost = $this->reader->getEntityBoost(new ValidTestEntityFloatBoost()); $this->assertEquals(1.4, $boost); } public function testGetBoost_BoostIsNull() { - $reader = new AnnotationReader(); - $boost = $reader->getEntityBoost(new ValidTestEntityNoBoost()); + $boost = $this->reader->getEntityBoost(new ValidTestEntityNoBoost()); $this->assertEquals(null, $boost); } public function testGetCallback_CallbackDefined() { - $reader = new AnnotationReader(); - $callback = $reader->getSynchronizationCallback(new ValidTestEntityFiltered()); + $callback = $this->reader->getSynchronizationCallback(new ValidTestEntityFiltered()); $this->assertEquals('shouldBeIndex', $callback); } public function testGetCallback_NoCallbackDefined() { - $reader = new AnnotationReader(); - $callback = $reader->getSynchronizationCallback(new ValidTestEntity()); + $callback = $this->reader->getSynchronizationCallback(new ValidTestEntity()); $this->assertEquals('', $callback); } @@ -165,8 +151,7 @@ public function testGetCallback_NoCallbackDefined() */ public function numericFieldTypeAreSupported() { - $reader = new AnnotationReader(); - $fields = $reader->getFields(new ValidTestEntityNumericFields()); + $fields = $this->reader->getFields(new ValidTestEntityNumericFields()); $this->assertEquals(4, count($fields)); @@ -184,8 +169,7 @@ public function numericFieldTypeAreSupported() */ public function getIndexFromAnnotationProperty() { - $reader = new AnnotationReader(); - $index = $reader->getDocumentIndex(new ValidTestEntityIndexProperty()); + $index = $this->reader->getDocumentIndex(new ValidTestEntityIndexProperty()); $this->assertEquals('my_core', $index); } @@ -195,8 +179,7 @@ public function getIndexFromAnnotationProperty() */ public function getIndexFromIndexHandler() { - $reader = new AnnotationReader(); - $index = $reader->getDocumentIndex(new ValidTestEntityIndexHandler()); + $index = $this->reader->getDocumentIndex(new ValidTestEntityIndexHandler()); $this->assertEquals('my_core', $index); } @@ -206,11 +189,84 @@ public function getIndexFromIndexHandler() */ public function readAnnotationsFromBaseClass() { - $reader = new AnnotationReader(); - $fields = $reader->getFields(new ChildEntity()); + $fields = $this->reader->getFields(new ChildEntity()); + + $this->assertEquals(3, count($fields)); + $this->assertTrue($this->reader->hasDocumentDeclaration(new ChildEntity())); + } + + /** + * @test + */ + public function readAnnotationsOfNestedObject() + { + $this->assertTrue($this->reader->hasDocumentDeclaration(new NestedObject())); + } + + /** + * @test + */ + public function readAnnotationsFromMultipleClassHierarchy() + { + $fields = $this->reader->getFields(new ChildEntity2()); + + $this->assertEquals(4, count($fields)); + } + + /** + * @test + */ + public function readGetterMethodWithParameters() + { + /** @var Field[] $fields */ + $fields = $this->reader->getFields(new EntityWithObject()); + + $this->assertCount(1, $fields); + $this->assertEquals('format(\'d.m.Y\')', $fields[0]->getGetterName()); + + $this->assertEquals('object_dt', $fields[0]->getNameWithAlias()); + + } + + /** + * @test + */ + public function checkIfPlainObjectIsNotDoctrineEntity() + { + $this->assertFalse($this->reader->isOrm(new ChildEntity()), 'is not a doctrine entity'); + } + + /** + * @test + */ + public function checkIfValidEntityIsDoctrineEntity() + { + $this->assertTrue($this->reader->isOrm(new ValidTestEntity()), 'is a doctrine entity'); + } + + /** + * @test + */ + public function checkIfPlainObjectIsNotDoctrineDocument() + { + $this->assertFalse($this->reader->isOdm(new ChildEntity()), 'is not a doctrine document'); + } + + /** + * @test + */ + public function checkIfValidDocumentIsDoctrineDocument() + { + $this->assertTrue($this->reader->isOdm(new ValidOdmTestDocument()), 'is a doctrine document'); + } - $this->assertEquals(2, count($fields)); - $this->assertTrue($reader->hasDocumentDeclaration(new ChildEntity())); + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + */ + public function methodWithAnnotationMustHaveAField() + { + $this->reader->getMethods(new EntityMissingNameProperty()); } } @@ -222,11 +278,16 @@ public function readAnnotationsFromBaseClass() */ abstract class BaseEntity { + /** + * @var mixed + */ + protected $baseField1; + /** * * @Solr\Field(type="integer") */ - private $field; + protected $baseField2; } class ChildEntity extends BaseEntity @@ -234,5 +295,41 @@ class ChildEntity extends BaseEntity /** * @Solr\Field(type="integer") */ - private $childField; + protected $baseField1; + + /** + * @Solr\Field(type="integer") + */ + protected $childField1; +} + +class ChildEntity2 extends ChildEntity +{ + /** + * @Solr\Field(type="integer") + */ + private $childField2; +} + +class EntityWithObject +{ + /** + * @Solr\Field(type="datetime", getter="format('d.m.Y')") + */ + private $object; +} + +/** + * @Solr\Nested() + */ +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/Annotation/Entities/EntityWithInvalidRepository.php b/Tests/Doctrine/Annotation/Entities/EntityWithInvalidRepository.php deleted file mode 100644 index 1354a71b..00000000 --- a/Tests/Doctrine/Annotation/Entities/EntityWithInvalidRepository.php +++ /dev/null @@ -1,14 +0,0 @@ -knownAliases = $this->getMock('FS\SolrBundle\Doctrine\ClassnameResolver\KnownNamespaceAliases', array(), array(), '', false); + $this->knownAliases = $this->createMock(KnownNamespaceAliases::class); } /** @@ -26,9 +28,7 @@ public function resolveClassnameOfCommonEntity() { $resolver = $this->getResolverWithKnowNamespace(self::ENTITY_NAMESPACE); - $expectedClass = 'FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity'; - - $this->assertEquals($expectedClass, $resolver->resolveFullQualifiedClassname('FSTest:ValidTestEntity')); + $this->assertEquals(ValidTestEntity::class, $resolver->resolveFullQualifiedClassname('FSTest:ValidTestEntity')); } /** diff --git a/Tests/Doctrine/ClassnameResolver/KnownNamespaceAliasesTest.php b/Tests/Doctrine/ClassnameResolver/KnownNamespaceAliasesTest.php index 4d899f10..ee50e4cb 100644 --- a/Tests/Doctrine/ClassnameResolver/KnownNamespaceAliasesTest.php +++ b/Tests/Doctrine/ClassnameResolver/KnownNamespaceAliasesTest.php @@ -3,21 +3,24 @@ namespace FS\SolrBundle\Tests\Doctrine\ClassnameResolver; +use Doctrine\ORM\Configuration as OrmConfiguration; +use Doctrine\ODM\MongoDB\Configuration as OdmConfiguration; use FS\SolrBundle\Doctrine\ClassnameResolver\KnownNamespaceAliases; -class KnownNamespaceAliasesTest extends \PHPUnit_Framework_TestCase { +class KnownNamespaceAliasesTest extends \PHPUnit_Framework_TestCase +{ /** * @test */ public function addAliasFromMultipleOrmConfigurations() { - $config1 = $this->getMock('Doctrine\ORM\Configuration', array(), array(), '', false); + $config1 = $this->createMock(OrmConfiguration::class); $config1->expects($this->once()) ->method('getEntityNamespaces') ->will($this->returnValue(array('AcmeDemoBundle'))); - $config2 = $this->getMock('Doctrine\ORM\Configuration', array(), array(), '', false); + $config2 = $this->createMock(OrmConfiguration::class); $config2->expects($this->once()) ->method('getEntityNamespaces') ->will($this->returnValue(array('AcmeBlogBundle'))); @@ -35,12 +38,12 @@ public function addAliasFromMultipleOrmConfigurations() */ public function addAliasFromMultipleOdmConfigurations() { - $config1 = $this->getMock('Doctrine\ODM\MongoDB\Configuration', array(), array(), '', false); + $config1 = $this->createMock(OdmConfiguration::class); $config1->expects($this->once()) ->method('getDocumentNamespaces') ->will($this->returnValue(array('AcmeDemoBundle'))); - $config2 = $this->getMock('Doctrine\ODM\MongoDB\Configuration', array(), array(), '', false); + $config2 = $this->createMock(OdmConfiguration::class); $config2->expects($this->once()) ->method('getDocumentNamespaces') ->will($this->returnValue(array('AcmeBlogBundle'))); @@ -58,12 +61,12 @@ public function addAliasFromMultipleOdmConfigurations() */ public function knowAliasHasAValidNamespace() { - $config1 = $this->getMock('Doctrine\ODM\MongoDB\Configuration', array(), array(), '', false); + $config1 = $this->createMock(OdmConfiguration::class); $config1->expects($this->once()) ->method('getDocumentNamespaces') ->will($this->returnValue(array('AcmeDemoBundle' => 'Acme\DemoBundle\Document'))); - $config2 = $this->getMock('Doctrine\ODM\MongoDB\Configuration', array(), array(), '', false); + $config2 = $this->createMock(OdmConfiguration::class); $config2->expects($this->once()) ->method('getDocumentNamespaces') ->will($this->returnValue(array('AcmeBlogBundle' => 'Acme\BlogBundle\Document'))); diff --git a/Tests/Doctrine/Hydration/DoctrineHydratorTest.php b/Tests/Doctrine/Hydration/DoctrineHydratorTest.php index 2d5564ac..7e3c756e 100644 --- a/Tests/Doctrine/Hydration/DoctrineHydratorTest.php +++ b/Tests/Doctrine/Hydration/DoctrineHydratorTest.php @@ -3,10 +3,22 @@ namespace FS\SolrBundle\Tests\Doctrine\Hydration; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Persistence\ObjectRepository; +use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; +use FS\SolrBundle\Doctrine\Annotation\Field; use FS\SolrBundle\Doctrine\Hydration\DoctrineHydrator; +use FS\SolrBundle\Doctrine\Hydration\DoctrineHydratorInterface; +use FS\SolrBundle\Doctrine\Hydration\DoctrineValueHydrator; +use FS\SolrBundle\Doctrine\Hydration\ValueHydrator; +use FS\SolrBundle\Doctrine\Mapper\MetaInformation; use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationInterface; use FS\SolrBundle\Tests\Doctrine\Mapper\SolrDocumentStub; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidOdmTestDocument; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; +use Symfony\Component\Validator\Constraints\Valid; /** * @group hydration @@ -14,6 +26,16 @@ class DoctrineHydratorTest extends \PHPUnit_Framework_TestCase { + /** + * @var AnnotationReader + */ + private $reader; + + public function setUp() + { + $this->reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader()); + } + /** * @test */ @@ -21,29 +43,58 @@ public function foundEntityInDbReplacesEntityOldTargetEntity() { $fetchedFromDoctrine = new ValidTestEntity(); - $repository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository'); + $repository = $this->createMock(ObjectRepository::class); $repository->expects($this->once()) ->method('find') ->with(1) ->will($this->returnValue($fetchedFromDoctrine)); $entity = new ValidTestEntity(); + $entity->setId(1); - $metainformations = new MetaInformationFactory(); + $metainformations = new MetaInformationFactory($this->reader); $metainformations = $metainformations->loadInformation($entity); - $doctrineRegistry = $this->setupDoctrineRegistry($metainformations, $repository); + $ormManager = $this->setupManager($metainformations, $repository); - $obj = new SolrDocumentStub(array()); - $obj->id = 1; + $obj = new SolrDocumentStub(array('id' => 'document_1')); - $hydrator = $this->getMock('FS\SolrBundle\Doctrine\Hydration\Hydrator'); - $hydrator->expects($this->once()) - ->method('hydrate') - ->with($obj, $metainformations) + $doctrine = new DoctrineHydrator(new ValueHydrator()); + $doctrine->setOrmManager($ormManager); + $hydratedDocument = $doctrine->hydrate($obj, $metainformations); + + $this->assertEntityFromDBReplcesTargetEntity($metainformations, $fetchedFromDoctrine, $hydratedDocument); + } + + /** + * @test + */ + public function useOdmManagerIfObjectIsOdmDocument() + { + $fetchedFromDoctrine = new ValidOdmTestDocument(); + + $odmRepository = $this->createMock(ObjectRepository::class); + $odmRepository->expects($this->once()) + ->method('find') + ->with(1) ->will($this->returnValue($fetchedFromDoctrine)); - $doctrine = new DoctrineHydrator($doctrineRegistry, $hydrator); + $entity = new ValidOdmTestDocument(); + $entity->setId(1); + + $metainformations = new MetaInformationFactory($this->reader); + $metainformations = $metainformations->loadInformation($entity); + + $ormManager = $this->createMock(ObjectManager::class); + $ormManager->expects($this->never()) + ->method('getRepository'); + $odmManager = $this->setupManager($metainformations, $odmRepository); + + $obj = new SolrDocumentStub(array('id' => 'document_1')); + + $doctrine = new DoctrineHydrator(new ValueHydrator()); + $doctrine->setOdmManager($odmManager); + $doctrine->setOrmManager($ormManager); $hydratedDocument = $doctrine->hydrate($obj, $metainformations); $this->assertEntityFromDBReplcesTargetEntity($metainformations, $fetchedFromDoctrine, $hydratedDocument); @@ -52,33 +103,70 @@ public function foundEntityInDbReplacesEntityOldTargetEntity() /** * @test */ - public function entityFromDbNotFoundShouldNotModifyMetainformations() + public function hydrationShouldOverwriteComplexTypes() { - $fetchedFromDoctrine = new ValidTestEntity(); + $entity1 = new ValidTestEntity(); + $entity1->setTitle('title 1'); + + $entity2 = new ValidTestEntity(); + $entity2->setTitle('title 2'); + + $relations = array($entity1, $entity2); + + $targetEntity = new ValidTestEntity(); + $targetEntity->setId(1); + $targetEntity->setPosts($relations); + + $metainformations = new MetaInformationFactory($this->reader); + $metainformations = $metainformations->loadInformation($targetEntity); + + $repository = $this->createMock(ObjectRepository::class); + $repository->expects($this->once()) + ->method('find') + ->with(1) + ->will($this->returnValue($targetEntity)); + + $ormManager = $this->setupManager($metainformations, $repository); - $repository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository'); + $obj = new SolrDocumentStub(array( + 'id' => 'document_1', + 'posts_ss' => array('title 1', 'title 2') + )); + + $doctrineHydrator = new DoctrineHydrator(new DoctrineValueHydrator()); + $doctrineHydrator->setOrmManager($ormManager); + + /** @var ValidTestEntity $hydratedEntity */ + $hydratedEntity = $doctrineHydrator->hydrate($obj, $metainformations); + + $this->assertEquals($relations, $hydratedEntity->getPosts()); + } + + /** + * @test + */ + public function entityFromDbNotFoundShouldNotModifyMetainformations() + { + $repository = $this->createMock(ObjectRepository::class); $repository->expects($this->once()) ->method('find') ->with(1) ->will($this->returnValue(null)); $entity = new ValidTestEntity(); + $entity->setId(1); - $metainformations = new MetaInformationFactory(); + $metainformations = new MetaInformationFactory($this->reader); $metainformations = $metainformations->loadInformation($entity); - $doctrineRegistry = $this->setupDoctrineRegistry($metainformations, $repository); + $ormManager = $this->setupManager($metainformations, $repository); - $obj = new SolrDocumentStub(array()); - $obj->id = 1; + $obj = new SolrDocumentStub(array('id' => 'document_1')); - $hydrator = $this->getMock('FS\SolrBundle\Doctrine\Hydration\Hydrator'); - $hydrator->expects($this->once()) - ->method('hydrate') - ->with($obj, $metainformations) - ->will($this->returnValue($fetchedFromDoctrine)); + $hydrator = new ValueHydrator(); - $doctrine = new DoctrineHydrator($doctrineRegistry, $hydrator); + $doctrine = new DoctrineHydrator($hydrator); + $doctrine->setOrmManager($ormManager); $hydratedDocument = $doctrine->hydrate($obj, $metainformations); $this->assertEquals($metainformations->getEntity(), $entity); @@ -87,9 +175,9 @@ public function entityFromDbNotFoundShouldNotModifyMetainformations() } /** - * @param $metainformations - * @param $fetchedFromDoctrine - * @param $hydratedDocument + * @param MetaInformation $metainformations + * @param object $fetchedFromDoctrine + * @param object $hydratedDocument */ private function assertEntityFromDBReplcesTargetEntity($metainformations, $fetchedFromDoctrine, $hydratedDocument) { @@ -98,24 +186,25 @@ private function assertEntityFromDBReplcesTargetEntity($metainformations, $fetch } /** - * @param $metainformations + * @param MetaInformationInterface $metainformations * @param $repository - * @return mixed + * + * @return \PHPUnit_Framework_MockObject_MockObject */ - private function setupDoctrineRegistry($metainformations, $repository) + private function setupManager($metainformations, $repository) { - $manager = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $manager = $this->createMock(ObjectManager::class); $manager->expects($this->once()) ->method('getRepository') ->with($metainformations->getClassName()) ->will($this->returnValue($repository)); - $doctrineRegistry = $this->getMock('Symfony\Bridge\Doctrine\RegistryInterface'); - $doctrineRegistry->expects($this->once()) + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry->expects($this->once()) ->method('getManager') ->will($this->returnValue($manager)); - return $doctrineRegistry; + return $managerRegistry; } } diff --git a/Tests/Doctrine/Hydration/DoctrineValueHydratorTest.php b/Tests/Doctrine/Hydration/DoctrineValueHydratorTest.php new file mode 100644 index 00000000..e4c3a26a --- /dev/null +++ b/Tests/Doctrine/Hydration/DoctrineValueHydratorTest.php @@ -0,0 +1,55 @@ +assertFalse($hydrator->mapValue('createdAt', array(), new MetaInformation())); + } + + /** + * @test + */ + public function skipObjects() + { + $hydrator = new DoctrineValueHydrator(); + + $field = new Field(array('type' => 'datetime')); + $field->name = 'createdAt'; + $field->getter = 'format(\'Y-m-d\TH:i:s.z\Z\')'; + + $metaInformation = new MetaInformation(); + $metaInformation->setFields(array($field)); + + $this->assertFalse($hydrator->mapValue('createdAt', new \DateTime(), $metaInformation)); + } + + /** + * @test + */ + public function mapCommonType() + { + $hydrator = new DoctrineValueHydrator(); + + $field = new Field(array('type' => 'string')); + $field->name = 'title'; + + $metaInformation = new MetaInformation(); + $metaInformation->setFields(array($field)); + + $this->assertTrue($hydrator->mapValue('title_s', 'a title', $metaInformation)); + } +} diff --git a/Tests/Doctrine/Hydration/NoDatabaseValueHydratorTest.php b/Tests/Doctrine/Hydration/NoDatabaseValueHydratorTest.php new file mode 100644 index 00000000..192e52c8 --- /dev/null +++ b/Tests/Doctrine/Hydration/NoDatabaseValueHydratorTest.php @@ -0,0 +1,37 @@ + '0003115-2231_S', + 'title_t' => 'fooo_bar' + )); + + $entity = new ValidTestEntity(); + + $metainformations = new MetaInformationFactory($reader); + $metainformations = $metainformations->loadInformation($entity); + + $entity = $hydrator->hydrate($document, $metainformations); + + $this->assertEquals('0003115-2231_S', $entity->getId()); + $this->assertEquals('fooo_bar', $entity->getTitle()); + } +} diff --git a/Tests/Doctrine/Hydration/ValueHydratorTest.php b/Tests/Doctrine/Hydration/ValueHydratorTest.php index f798cffd..e06522ef 100644 --- a/Tests/Doctrine/Hydration/ValueHydratorTest.php +++ b/Tests/Doctrine/Hydration/ValueHydratorTest.php @@ -3,29 +3,47 @@ namespace FS\SolrBundle\Tests\Doctrine\Hydration; +use Doctrine\Common\Collections\ArrayCollection; +use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; use FS\SolrBundle\Doctrine\Hydration\ValueHydrator; +use FS\SolrBundle\Doctrine\Hydration\ValueHydratorInterface; use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; use FS\SolrBundle\Tests\Doctrine\Mapper\SolrDocumentStub; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithRelation; /** * @group hydration */ class ValueHydratorTest extends \PHPUnit_Framework_TestCase { + /** + * @var AnnotationReader + */ + private $reader; + + public function setUp() + { + $this->reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader()); + } + /** * @test */ public function documentShouldMapToEntity() { $obj = new SolrDocumentStub(array( - 'id' => 1, - 'title_t' => 'foo' + 'id' => 'document_1', + 'title_t' => 'foo', + 'publish_date_s' => '10.10.2016', + 'field_s' => 'value 1234', + 'unknown_field_s' => 'value' )); $entity = new ValidTestEntity(); - $metainformations = new MetaInformationFactory(); + $metainformations = new MetaInformationFactory($this->reader); $metainformations = $metainformations->loadInformation($entity); $hydrator = new ValueHydrator(); @@ -34,6 +52,8 @@ public function documentShouldMapToEntity() $this->assertTrue($hydratedDocument instanceof $entity); $this->assertEquals(1, $entity->getId()); $this->assertEquals('foo', $entity->getTitle()); + $this->assertEquals('10.10.2016', $entity->getPublishDate()); + $this->assertEquals('value 1234', $entity->getField()); } /** @@ -42,13 +62,13 @@ public function documentShouldMapToEntity() public function underscoreFieldBecomeCamelCase() { $obj = new SolrDocumentStub(array( - 'id' => 1, + 'id' => 'document_1', 'created_at_d' => 12345 )); $entity = new ValidTestEntity(); - $metainformations = new MetaInformationFactory(); + $metainformations = new MetaInformationFactory($this->reader); $metainformations = $metainformations->loadInformation($entity); $hydrator = new ValueHydrator(); @@ -58,5 +78,60 @@ public function underscoreFieldBecomeCamelCase() $this->assertEquals(1, $entity->getId()); $this->assertEquals(12345, $entity->getCreatedAt()); } + + /** + * @test + */ + public function doNotOverwriteComplexTypes_Collection() + { + $obj = new SolrDocumentStub(array( + 'id' => 'document_1', + 'title_t' => 'foo', + 'collection_ss' => array('title 1', 'title 2') + )); + + $entity = new ValidTestEntityWithCollection(); + + $metainformations = new MetaInformationFactory($this->reader); + $metainformations = $metainformations->loadInformation($entity); + + $hydrator = new ValueHydrator(); + $hydratedDocument = $hydrator->hydrate($obj, $metainformations); + + $this->assertTrue($hydratedDocument instanceof $entity); + $this->assertEquals(1, $entity->getId()); + $this->assertEquals('foo', $entity->getTitle()); + $this->assertEquals(array('title 1', 'title 2'), $entity->getCollection()); + } + + /** + * @test + */ + public function doNotOverwriteComplexTypes_Relation() + { + $obj = new SolrDocumentStub(array( + 'id' => 'document_1', + 'title_t' => 'foo', + 'posts_ss' => array('title 1', 'title2') + )); + + $entity1 = new ValidTestEntity(); + $entity1->setTitle('title 1'); + + $entity = new ValidTestEntityWithRelation(); + $entity->setRelation($entity1); + + $metainformations = new MetaInformationFactory($this->reader); + $metainformations = $metainformations->loadInformation($entity); + + $hydrator = new ValueHydrator(); + $hydratedDocument = $hydrator->hydrate($obj, $metainformations); + + $this->assertTrue($hydratedDocument instanceof $entity); + $this->assertEquals(1, $entity->getId()); + $this->assertEquals('foo', $entity->getTitle()); + + $this->assertTrue($hydratedDocument->getRelation() === $entity1); + } } \ No newline at end of file diff --git a/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php new file mode 100644 index 00000000..9d366ee7 --- /dev/null +++ b/Tests/Doctrine/Mapper/EntityMapperObjectRelationTest.php @@ -0,0 +1,376 @@ +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 NestedEntity(); + $collectionItem1->setId(uniqid()); + $collectionItem1->setName('title 1'); + + $collectionItem2 = new NestedEntity(); + $collectionItem2->setId(uniqid()); + $collectionItem2->setName('title 2'); + + $collection = new ArrayCollection([$collectionItem1, $collectionItem2]); + + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollectionValidGetter($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; + + $this->assertCollectionItemsMappedProperly($collectionField, 1); + } + + /** + * @test + */ + public function doNotIndexEmptyNestedCollection() + { + $collection = new ArrayCollection([]); + + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollectionValidGetter($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayNotHasKey('_childDocuments_', $document->getFields()); + } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage No method "unknown()" found in class "FS\SolrBundle\Tests\Fixtures\EntityNestedProperty" + */ + public function throwExceptionIfConfiguredGetterDoesNotExists() + { + $collection = new ArrayCollection([new \DateTime(), new \DateTime()]); + + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollectionInvalidGetter($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $this->mapper->toDocument($metaInformation); + } + + /** + * @test + */ + public function mapRelationFieldAllFields() + { + $collectionItem1 = new NestedEntity(); + $collectionItem1->setId(uniqid()); + $collectionItem1->setName('title 1'); + + $collectionItem2 = new NestedEntity(); + $collectionItem2->setId(uniqid()); + $collectionItem2->setName('title 2'); + + $collection = [$collectionItem1, $collectionItem2]; + + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setCollection($collection); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('_childDocuments_', $document->getFields()); + $collectionField = $document->getFields()['_childDocuments_']; + + $this->assertCollectionItemsMappedProperly($collectionField, 2); + } + + /** + * @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 + */ + 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 + */ + public function mapRelationField_Getter() + { + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + + $object = new NestedEntity(); + $object->setId(uniqid()); + $object->setName('nested entity'); + + $entity->setSimpleGetter($object); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $document = $this->mapper->toDocument($metaInformation); + + $this->assertArrayHasKey('simple_getter_s', $document->getFields()); + + $collectionField = $document->getFields()['simple_getter_s']; + + $this->assertEquals('nested entity', $collectionField); + } + + /** + * @test + */ + public function callGetterWithParameter_ObjectProperty() + { + $date = new \DateTime(); + + $entity = new EntityNestedProperty(); + $entity->setId(uniqid()); + $entity->setGetterWithParameters($date); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $fields = $metaInformation->getFields(); + $metaInformation->setFields($fields); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + $this->assertArrayHasKey('getter_with_parameters_dt', $fields); + $this->assertEquals($date->format('d.m.Y'), $fields['getter_with_parameters_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->setComplexDataType(json_encode($data)); + + $metaInformation = $this->metaInformationFactory->loadInformation($entity1); + + $document = $this->mapper->toDocument($metaInformation); + + $fields = $document->getFields(); + + $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); + } + + /** + * @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 + */ + private function assertCollectionItemsMappedProperly($collectionField, $expectedItems) + { + $this->assertEquals($expectedItems, count($collectionField), 'should be 2 collection items'); + + foreach ($collectionField as $item) { + $this->assertArrayHasKey('id', $item); + $this->assertArrayHasKey('name_t', $item); + $this->assertEquals(2, count($item), 'field has 2 properties'); + } + } +} + +/** @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); + } + + 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 38dec220..7cd18916 100644 --- a/Tests/Doctrine/Mapper/EntityMapperTest.php +++ b/Tests/Doctrine/Mapper/EntityMapperTest.php @@ -2,10 +2,26 @@ namespace FS\SolrBundle\Tests\Doctrine\Mapper; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Persistence\ObjectRepository; use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; +use FS\SolrBundle\Doctrine\Hydration\DoctrineHydrator; use FS\SolrBundle\Doctrine\Hydration\HydrationModes; +use FS\SolrBundle\Doctrine\Hydration\HydratorInterface; +use FS\SolrBundle\Doctrine\Hydration\IndexHydrator; +use FS\SolrBundle\Doctrine\Hydration\NoDatabaseValueHydrator; +use FS\SolrBundle\Doctrine\Hydration\ValueHydrator; use FS\SolrBundle\Doctrine\Mapper\EntityMapper; -use FS\SolrBundle\Doctrine\Mapper\Mapping\MapAllFieldsCommand; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; +use FS\SolrBundle\Doctrine\Annotation\Field; +use FS\SolrBundle\Tests\Fixtures\EntityWithCustomId; +use FS\SolrBundle\Tests\Fixtures\PartialUpdateEntity; +use FS\SolrBundle\Tests\Fixtures\ValidOdmTestDocument; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithCollection; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityWithRelation; use FS\SolrBundle\Tests\Util\MetaTestInformationFactory; use Solarium\QueryType\Update\Query\Document\Document; @@ -19,31 +35,48 @@ class EntityMapperTest extends \PHPUnit_Framework_TestCase private $doctrineHydrator = null; private $indexHydrator = null; - public function setUp() - { - $this->doctrineHydrator = $this->getMock('FS\SolrBundle\Doctrine\Hydration\Hydrator'); - $this->indexHydrator = $this->getMock('FS\SolrBundle\Doctrine\Hydration\Hydrator'); - } + /** + * @var MetaInformationFactory + */ + private $metaInformationFactory; + + /** + * @var EntityMapper + */ + private $mapper; - public function testToDocument_EntityMayNotIndexed() + public function setUp() { - $mapper = new \FS\SolrBundle\Doctrine\Mapper\EntityMapper($this->doctrineHydrator, $this->indexHydrator); + $this->doctrineHydrator = $this->createMock(HydratorInterface::class); + $this->indexHydrator = $this->createMock(HydratorInterface::class); + $this->metaInformationFactory = new MetaInformationFactory(new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); - $actual = $mapper->toDocument(MetaTestInformationFactory::getMetaInformation()); - $this->assertNull($actual); + $this->mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator, $this->metaInformationFactory); } public function testToDocument_DocumentIsUpdated() { - $mapper = new \FS\SolrBundle\Doctrine\Mapper\EntityMapper($this->doctrineHydrator, $this->indexHydrator); - $mapper->setMappingCommand(new MapAllFieldsCommand(new AnnotationReader())); - $actual = $mapper->toDocument(MetaTestInformationFactory::getMetaInformation()); + $actual = $this->mapper->toDocument(MetaTestInformationFactory::getMetaInformation()); $this->assertTrue($actual instanceof Document); $this->assertNotNull($actual->id); } + /** + * @test + */ + public function setFieldModifier() + { + $entity = new PartialUpdateEntity(); + $entity->setId(uniqid()); + + $actualDocument = $this->mapper->toDocument($this->metaInformationFactory->loadInformation($entity)); + + $this->assertEquals('set', $actualDocument->getFieldModifier('subtitle')); + $this->assertNull($actualDocument->getFieldModifier('title')); + } + public function testToEntity_WithDocumentStub_HydrateIndexOnly() { $targetEntity = new ValidTestEntity(); @@ -55,41 +88,175 @@ public function testToEntity_WithDocumentStub_HydrateIndexOnly() $this->doctrineHydrator->expects($this->never()) ->method('hydrate'); - $mapper = new \FS\SolrBundle\Doctrine\Mapper\EntityMapper($this->doctrineHydrator, $this->indexHydrator); - $mapper->setHydrationMode(HydrationModes::HYDRATE_INDEX); - $entity = $mapper->toEntity(new SolrDocumentStub(), $targetEntity); + $this->mapper->setHydrationMode(HydrationModes::HYDRATE_INDEX); + $entity = $this->mapper->toEntity(new SolrDocumentStub(), $targetEntity); $this->assertTrue($entity instanceof $targetEntity); } - public function testToEntity_ConcreteDocumentClass_WithDoctrine() + public function testToEntity_ConcreteDocumentClass_WithDoctrineOrm() { $targetEntity = new ValidTestEntity(); + $targetEntity->setField('a value'); - $this->indexHydrator->expects($this->once()) - ->method('hydrate') - ->will($this->returnValue($targetEntity)); + $this->indexHydrator = new IndexHydrator(new NoDatabaseValueHydrator()); - $this->doctrineHydrator->expects($this->once()) - ->method('hydrate') - ->will($this->returnValue($targetEntity)); + $this->doctrineHydrator = new DoctrineHydrator(new ValueHydrator()); + $this->doctrineHydrator->setOrmManager($this->setupOrmManager($targetEntity, 1)); + + $this->mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator, $this->metaInformationFactory); + $this->mapper->setHydrationMode(HydrationModes::HYDRATE_DOCTRINE); + $entity = $this->mapper->toEntity(new Document(array('id' => 'document_1', 'title' => 'value from index')), $targetEntity); - $mapper = new \FS\SolrBundle\Doctrine\Mapper\EntityMapper($this->doctrineHydrator, $this->indexHydrator); - $mapper->setHydrationMode(HydrationModes::HYDRATE_DOCTRINE); - $entity = $mapper->toEntity(new Document(array()), $targetEntity); + $this->assertTrue($entity instanceof $targetEntity); + + $this->assertEquals('a value', $entity->getField()); + $this->assertEquals('value from index', $entity->getTitle()); + } + + public function testToEntity_ConcreteDocumentClass_WithDoctrineOdm() + { + $targetEntity = new ValidOdmTestDocument(); + $targetEntity->setField('a value'); + + $this->indexHydrator = new IndexHydrator(new NoDatabaseValueHydrator()); + + $this->doctrineHydrator = new DoctrineHydrator(new ValueHydrator()); + $this->doctrineHydrator->setOdmManager($this->setupOdmManager($targetEntity, 1)); + + $this->mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator, $this->metaInformationFactory); + $this->mapper->setHydrationMode(HydrationModes::HYDRATE_DOCTRINE); + $entity = $this->mapper->toEntity(new Document(array('id' => 'document_1', 'title' => 'value from index')), $targetEntity); $this->assertTrue($entity instanceof $targetEntity); + + $this->assertEquals('a value', $entity->getField()); + $this->assertEquals('value from index', $entity->getTitle()); } - public function ToCamelCase() + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage Please check your config. Given entity is not a Doctrine entity, but Doctrine hydration is enabled. Use setHydrationMode(HydrationModes::HYDRATE_DOCTRINE) to fix this. + */ + public function throwExceptionIfGivenObjectIsNotEntityButItShould() { - $mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator); + $targetEntity = new PlainObject(); + + $this->indexHydrator = new IndexHydrator(new NoDatabaseValueHydrator()); - $meta = new \ReflectionClass($mapper); - $method = $meta->getMethod('toCamelCase'); - $method->setAccessible(true); - $calmelCased = $method->invoke($mapper, 'test_underline'); - $this->assertEquals('testUnderline', $calmelCased); + $this->doctrineHydrator = new DoctrineHydrator(new ValueHydrator()); + + $this->mapper->toEntity(new Document(array('id' => 'document_1', 'title' => 'value from index')), $targetEntity); + } + + /** + * @test + */ + public function generatedDocumentIdIfRequired() + { + $entity = new EntityWithCustomId(); + + $this->indexHydrator = new IndexHydrator(new NoDatabaseValueHydrator()); + + $this->doctrineHydrator = new DoctrineHydrator(new ValueHydrator()); + + $metainformation = $this->metaInformationFactory->loadInformation($entity); + + $this->mapper = new EntityMapper($this->doctrineHydrator, $this->indexHydrator, $this->metaInformationFactory); + $document = $this->mapper->toDocument($metainformation); + + $fields = $document->getFields(); + $this->assertArrayHasKey('id', $fields); + $this->assertNotNull($fields['id']); + } + + public function testMapEntity_DocumentShouldContainThreeFields() + { + $document = $this->mapper->toDocument(MetaTestInformationFactory::getMetaInformation()); + + $this->assertTrue($document instanceof Document, 'is a Document'); + $this->assertEquals(4, $document->count(), 'three fields are mapped'); + + $this->assertEquals(1, $document->getBoost(), 'document boost should be 1'); + + $boostTitleField = $document->getFieldBoost('title'); + $this->assertEquals(1.8, $boostTitleField, 'boost value of field title_s should be 1.8'); + + $this->assertArrayHasKey('id', $document); + $this->assertArrayHasKey('title', $document); + $this->assertArrayHasKey('text_t', $document); + $this->assertArrayHasKey('created_at_dt', $document); + } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage No entity id set for "FS\SolrBundle\Tests\Fixtures\ValidTestEntity" + */ + public function throwExceptionIfEntityHasNoId() + { + $entity = new ValidTestEntity; + + $metaInformation = $this->metaInformationFactory->loadInformation($entity); + + $this->mapper->toDocument($metaInformation); + } + + private function setupOrmManager($entity, $expectedEntityId) + { + $repository = $this->createMock(ObjectRepository::class); + $repository->expects($this->once()) + ->method('find') + ->with($expectedEntityId) + ->will($this->returnValue($entity)); + + $manager = $this->createMock(ObjectManager::class); + $manager->expects($this->once()) + ->method('getRepository') + ->will($this->returnValue($repository)); + + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry->expects($this->once()) + ->method('getManager') + ->will($this->returnValue($manager)); + + return $managerRegistry; + } + + private function setupOdmManager($entity, $expectedEntityId) + { + $repository = $this->createMock(ObjectRepository::class); + $repository->expects($this->once()) + ->method('find') + ->with($expectedEntityId) + ->will($this->returnValue($entity)); + + $manager = $this->createMock(ObjectManager::class); + $manager->expects($this->once()) + ->method('getRepository') + ->will($this->returnValue($repository)); + + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry->expects($this->once()) + ->method('getManager') + ->will($this->returnValue($manager)); + + return $managerRegistry; } } +use FS\SolrBundle\Doctrine\Annotation as Solr; + +/** + * @Solr\Document(boost="1") + */ +class PlainObject +{ + /** + * @var int + * + * @Solr\Id + */ + private $id; +} \ No newline at end of file diff --git a/Tests/Doctrine/Mapper/Mapping/MapAllFieldsCommandTest.php b/Tests/Doctrine/Mapper/Mapping/MapAllFieldsCommandTest.php deleted file mode 100644 index 85a6ec55..00000000 --- a/Tests/Doctrine/Mapper/Mapping/MapAllFieldsCommandTest.php +++ /dev/null @@ -1,37 +0,0 @@ -createDocument(MetaTestInformationFactory::getMetaInformation()); - - $this->assertTrue($actual instanceof Document, 'is a Document'); - $this->assertFieldCount(3, $actual, 'three fields are mapped'); - - $this->assertEquals(1, $actual->getBoost(), 'document boost should be 1'); - - $boostTitleField = $actual->getFieldBoost('title_s'); - $this->assertEquals(1.8, $boostTitleField, 'boost value of field title_s should be 1.8'); - - $this->assertHasDocumentFields($actual, self::$MAPPED_FIELDS); - } -} - diff --git a/Tests/Doctrine/Mapper/Mapping/MapIdentifierCommandTest.php b/Tests/Doctrine/Mapper/Mapping/MapIdentifierCommandTest.php deleted file mode 100644 index 1292c2f6..00000000 --- a/Tests/Doctrine/Mapper/Mapping/MapIdentifierCommandTest.php +++ /dev/null @@ -1,29 +0,0 @@ -createDocument(MetaTestInformationFactory::getMetaInformation()); - - $this->assertEquals(2, $document->count(), 'fieldcount is two'); - $this->assertEquals(2, $document->id, 'id is 2'); - - } - -} - diff --git a/Tests/Doctrine/Mapper/Mapping/SolrDocumentTest.php b/Tests/Doctrine/Mapper/Mapping/SolrDocumentTest.php deleted file mode 100644 index d819880e..00000000 --- a/Tests/Doctrine/Mapper/Mapping/SolrDocumentTest.php +++ /dev/null @@ -1,23 +0,0 @@ -getFields(); - foreach ($expectedFields as $expectedField) { - $this->assertTrue(array_key_exists($expectedField, $actualFields), 'field' . $expectedField . ' not in document'); - } - } - - protected function assertFieldCount($expectedCount, Document $document, $message = '') - { - $this->assertEquals($expectedCount + self::FIELDS_ALWAYS_MAPPED, $document->count(), $message); - } -} - diff --git a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php index a98c9a3c..6fadad25 100644 --- a/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php +++ b/Tests/Doctrine/Mapper/MetaInformationFactoryTest.php @@ -1,9 +1,18 @@ reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader()); + } + private function getClassnameResolver($namespace) { - $doctrineConfiguration = $this->getMock('FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolver', array(), array(), '', false); + $doctrineConfiguration = $this->createMock(ClassnameResolver::class); $doctrineConfiguration->expects($this->any()) ->method('resolveFullQualifiedClassname') ->will($this->returnValue($namespace)); @@ -23,7 +42,7 @@ private function getClassnameResolver($namespace) private function getClassnameResolverCouldNotResolveClassname() { - $doctrineConfiguration = $this->getMock('FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolver', array(), array(), '', false); + $doctrineConfiguration = $this->createMock(ClassnameResolver::class); $doctrineConfiguration->expects($this->any()) ->method('resolveFullQualifiedClassname') ->will($this->throwException(new ClassnameResolverException('could not resolve classname for entity'))); @@ -38,17 +57,17 @@ public function testLoadInformation_ShouldLoadAll() $expectedDocumentName = 'validtestentity'; - $classnameResolver = $this->getClassnameResolver('FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity'); + $classnameResolver = $this->getClassnameResolver(ValidTestEntity::class); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($classnameResolver); $actual = $factory->loadInformation('FSBlogBundle:ValidTestEntity'); $this->assertTrue($actual instanceof MetaInformation); $this->assertEquals($expectedClassName, $actual->getClassName(), 'wrong classname'); $this->assertEquals($expectedDocumentName, $actual->getDocumentName(), 'wrong documentname'); - $this->assertEquals(4, count($actual->getFields()), '4 fields are set'); - $this->assertEquals(5, count($actual->getFieldMapping()), '5 fields are mapped'); + $this->assertEquals(5, count($actual->getFields()), '5 fields are set'); + $this->assertEquals(6, count($actual->getFieldMapping()), '5 fields are mapped'); } public function testLoadInformation_LoadInformationFromObject() @@ -58,49 +77,54 @@ public function testLoadInformation_LoadInformationFromObject() $expectedDocumentName = 'validtestentity'; - $doctrineConfiguration = $this->getClassnameResolver('FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity'); + $doctrineConfiguration = $this->getClassnameResolver(ValidTestEntity::class); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($doctrineConfiguration); $actual = $factory->loadInformation($testEntity); $this->assertTrue($actual instanceof MetaInformation); $this->assertEquals($expectedClassName, $actual->getClassName(), 'wrong classname'); $this->assertEquals($expectedDocumentName, $actual->getDocumentName(), 'wrong documentname'); - $this->assertEquals(4, count($actual->getFields()), '4 fields are mapped'); + $this->assertEquals(5, count($actual->getFields()), '5 fields are mapped'); + + $this->assertTrue($actual->hasField('title'), 'field should be able to located by field-name'); + $this->assertTrue($actual->hasField('text_t'), 'field should be able to located by field-name with suffix'); + + $this->assertTrue($actual->getField('title') instanceof Field); } /** - * @expectedException RuntimeException + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException * @expectedExceptionMessage no declaration for document found in entity */ public function testLoadInformation_EntityHasNoDocumentDeclaration_ShouldThrowException() { - $doctrineConfiguration = $this->getClassnameResolver('FS\SolrBundle\Tests\Doctrine\Mapper\NotIndexedEntity'); + $doctrineConfiguration = $this->getClassnameResolver(NotIndexedEntity::class); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($doctrineConfiguration); $factory->loadInformation('FSBlogBundle:NotIndexedEntity'); } /** - * @expectedException FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolverException + * @expectedException \FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolverException * @expectedExceptionMessage could not resolve classname for entity */ public function testLoadInformation_EntityDoesNoExists() { $doctrineConfiguration = $this->getClassnameResolverCouldNotResolveClassname(); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($doctrineConfiguration); $factory->loadInformation('FSBlogBundle:UnknownEntity'); } public function testLoadInformation_FromObject() { - $doctrineConfiguration = $this->getClassnameResolver('FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity'); + $doctrineConfiguration = $this->getClassnameResolver(ValidTestEntity::class); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($doctrineConfiguration); $testEntity = new ValidTestEntity(); @@ -112,9 +136,9 @@ public function testLoadInformation_FromObject() public function testLoadInformation_FromFullClassname() { - $doctrineConfiguration = $this->getClassnameResolver('FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity'); + $doctrineConfiguration = $this->getClassnameResolver(ValidTestEntity::class); - $factory = new MetaInformationFactory(); + $factory = new MetaInformationFactory($this->reader); $factory->setClassnameResolver($doctrineConfiguration); $entityClassname = get_class(new ValidTestEntity()); @@ -123,5 +147,60 @@ public function testLoadInformation_FromFullClassname() $expected = $entityClassname; $this->assertEquals($expected, $informations->getClassName(), 'class from fullclassname not discovered'); } + + /** + * @test + */ + public function determineDoctrineMapperTypeFromEntity() + { + $factory = new MetaInformationFactory($this->reader); + $metainformation = $factory->loadInformation(new ValidTestEntity()); + + $this->assertEquals(MetaInformationInterface::DOCTRINE_MAPPER_TYPE_RELATIONAL, $metainformation->getDoctrineMapperType()); + } + + /** + * @test + */ + public function determineDoctrineMapperTypeFromDocument() + { + $factory = new MetaInformationFactory($this->reader); + $metainformation = $factory->loadInformation(new ValidOdmTestDocument()); + + $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()); + } + + /** + * @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->assertArrayNotHasKey('collection', $metainformation->getFieldMapping()); + $this->assertArrayHasKey('collection.id', $metainformation->getFieldMapping()); + $this->assertArrayHasKey('collection.name_t', $metainformation->getFieldMapping()); + + + } } diff --git a/Tests/Doctrine/Mapper/MetaInformationTest.php b/Tests/Doctrine/Mapper/MetaInformationTest.php index 339d9fdf..f50e2ab4 100644 --- a/Tests/Doctrine/Mapper/MetaInformationTest.php +++ b/Tests/Doctrine/Mapper/MetaInformationTest.php @@ -18,55 +18,6 @@ private function createFieldObject($name, $value) return $value; } - public function testHasField_FieldExists() - { - $value1 = $this->createFieldObject('field1', 'oldfieldvalue'); - $value2 = $this->createFieldObject('field2', true); - - $fields = array( - 'field1' => $value1, - 'field2' => $value2 - ); - - $information = new MetaInformation(); - $information->setFields($fields); - - $this->assertTrue($information->hasField('field2'), 'metainformation should have field2'); - } - - public function testHasField_FieldNotExists() - { - $value1 = $this->createFieldObject('field1', 'oldfieldvalue'); - - $fields = array( - 'field1' => $value1, - ); - - $information = new MetaInformation(); - $information->setFields($fields); - - $this->assertFalse($information->hasField('field2'), 'unknown field field2'); - } - - public function testSetFieldValue() - { - $value1 = $this->createFieldObject('field1', 'oldfieldvalue'); - $value2 = $this->createFieldObject('field2', true); - - $fields = array( - 'field1' => $value1, - 'field2' => $value2 - ); - - $information = new MetaInformation(); - $information->setFields($fields); - - $expectedValue = 'newFieldValue'; - $information->setFieldValue('field2', $expectedValue); - - $this->assertEquals($expectedValue, $information->getField('field2')->value, 'field2 should have new value'); - } - public function testHasCallback_CallbackSet() { $information = new MetaInformation(); diff --git a/Tests/Doctrine/Mapper/NoIdEntity.php b/Tests/Doctrine/Mapper/NoIdEntity.php deleted file mode 100644 index 466b947c..00000000 --- a/Tests/Doctrine/Mapper/NoIdEntity.php +++ /dev/null @@ -1,29 +0,0 @@ -id; - } -} - diff --git a/Tests/Doctrine/Mapper/NotIndexedEntity.php b/Tests/Doctrine/Mapper/NotIndexedEntity.php deleted file mode 100644 index 2ceab1ee..00000000 --- a/Tests/Doctrine/Mapper/NotIndexedEntity.php +++ /dev/null @@ -1,8 +0,0 @@ -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); + } + + /** + * @test + */ + public function doNotFailHardIfNormalEntityIsPersisted() + { + $this->solr->expects($this->never()) + ->method('addDocument'); + + $this->solr->expects($this->never()) + ->method('removeDocument'); + + $entity = new NotIndexedEntity(); + + $objectManager = $this->createMock(EntityManagerInterface::class); + + $lifecycleEventArgs = new LifecycleEventArgs($entity, $objectManager); + + $this->subscriber->postPersist($lifecycleEventArgs); + $this->subscriber->preRemove($lifecycleEventArgs); + + + + $this->subscriber->postFlush(new PostFlushEventArgs($objectManager)); + } +} diff --git a/Tests/DocumentStub.php b/Tests/DocumentStub.php index 6fe17962..b134b8ce 100644 --- a/Tests/DocumentStub.php +++ b/Tests/DocumentStub.php @@ -19,4 +19,24 @@ public function __construct(array $fields = array(), array $boosts = array(), ar { } + + /** + * @param string $fieldName + * + * @return mixed + */ + public function getField($fieldName) + { + $fields = array('id' => $this->id, 'document_name' => $this->document_name_s); + + return $fields[$fieldName]; + } + + /** + * @return array + */ + public function getFields() + { + return array('id' => $this->id, 'document_name' => $this->document_name_s); + } } \ No newline at end of file diff --git a/Tests/Fixtures/EntityCore0.php b/Tests/Fixtures/EntityCore0.php new file mode 100644 index 00000000..157881b7 --- /dev/null +++ b/Tests/Fixtures/EntityCore0.php @@ -0,0 +1,57 @@ +id; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + +} \ No newline at end of file diff --git a/Tests/Fixtures/EntityCore1.php b/Tests/Fixtures/EntityCore1.php new file mode 100644 index 00000000..0f11ee61 --- /dev/null +++ b/Tests/Fixtures/EntityCore1.php @@ -0,0 +1,57 @@ +id; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + +} \ No newline at end of file diff --git a/Tests/Fixtures/EntityNestedProperty.php b/Tests/Fixtures/EntityNestedProperty.php new file mode 100644 index 00000000..f1fe0f02 --- /dev/null +++ b/Tests/Fixtures/EntityNestedProperty.php @@ -0,0 +1,142 @@ +id; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + public function sliceCollection() + { + return [$this->collectionValidGetter[0]]; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + + /** + * @param array $collection + */ + public function setCollection($collection) + { + $this->collection = $collection; + } + + /** + * @param object $nestedProperty + */ + public function setNestedProperty($nestedProperty) + { + $this->nestedProperty = $nestedProperty; + } + + /** + * @param array $collectionValidGetter + */ + public function setCollectionValidGetter($collectionValidGetter) + { + $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/EntityWithCustomId.php b/Tests/Fixtures/EntityWithCustomId.php new file mode 100644 index 00000000..05a0fb1e --- /dev/null +++ b/Tests/Fixtures/EntityWithCustomId.php @@ -0,0 +1,31 @@ +id; + } +} \ No newline at end of file diff --git a/Tests/Fixtures/EntityWithInvalidRepository.php b/Tests/Fixtures/EntityWithInvalidRepository.php new file mode 100644 index 00000000..5a3969fc --- /dev/null +++ b/Tests/Fixtures/EntityWithInvalidRepository.php @@ -0,0 +1,18 @@ +id; + } + + /** + * @param mixed $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + +} \ No newline at end of file diff --git a/Tests/Fixtures/NotIndexedEntity.php b/Tests/Fixtures/NotIndexedEntity.php new file mode 100644 index 00000000..cdc5511c --- /dev/null +++ b/Tests/Fixtures/NotIndexedEntity.php @@ -0,0 +1,9 @@ +subtitle; + } + + /** + * @param string $subtitle + */ + public function setSubtitle($subtitle) + { + $this->subtitle = $subtitle; + } +} \ No newline at end of file diff --git a/Tests/Doctrine/Annotation/Entities/ValidEntityRepository.php b/Tests/Fixtures/ValidEntityRepository.php similarity index 62% rename from Tests/Doctrine/Annotation/Entities/ValidEntityRepository.php rename to Tests/Fixtures/ValidEntityRepository.php index ec85d06f..4c7e8191 100644 --- a/Tests/Doctrine/Annotation/Entities/ValidEntityRepository.php +++ b/Tests/Fixtures/ValidEntityRepository.php @@ -1,5 +1,6 @@ id; + } + + /** + * @param int $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string $text + */ + public function getText() + { + return $this->text; + } + + /** + * @return string $title + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @param string $costomField + */ + public function setCostomField($costomField) + { + $this->costomField = $costomField; + } + + /** + * @return string + */ + public function getCostomField() + { + return $this->costomField; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->created_at; + } + + /** + * @param \DateTime $created_at + */ + public function setCreatedAt($created_at) + { + $this->created_at = $created_at; + } + + /** + * @return ValidTestEntity[] + */ + public function getPosts() + { + return $this->posts; + } + + /** + * @param ValidTestEntity[] $posts + */ + public function setPosts($posts) + { + $this->posts = $posts; + } + + /** + * @param string $field + */ + public function setField($field) + { + $this->privateField = $field; + } + + /** + * @return string + */ + public function getField() + { + return $this->privateField; + } + + /** + * @return string + */ + public function getPublishDate() + { + return $this->publishDate; + } + + /** + * @param string $publishDate + */ + public function setPublishDate($publishDate) + { + $this->publishDate = $publishDate; + } +} + diff --git a/Tests/Fixtures/ValidTestEntity.php b/Tests/Fixtures/ValidTestEntity.php new file mode 100644 index 00000000..af79518d --- /dev/null +++ b/Tests/Fixtures/ValidTestEntity.php @@ -0,0 +1,219 @@ +id; + } + + /** + * @param int $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string $text + */ + public function getText() + { + return $this->text; + } + + /** + * @return string $title + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @param string $costomField + */ + public function setCostomField($costomField) + { + $this->costomField = $costomField; + } + + /** + * @return string + */ + public function getCostomField() + { + return $this->costomField; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->created_at; + } + + /** + * @param \DateTime $created_at + */ + public function setCreatedAt($created_at) + { + $this->created_at = $created_at; + } + + /** + * @return ValidTestEntity[] + */ + public function getPosts() + { + return $this->posts; + } + + /** + * @param ValidTestEntity[] $posts + */ + public function setPosts($posts) + { + $this->posts = $posts; + } + + /** + * @param string $field + */ + public function setField($field) + { + $this->privateField = $field; + } + + /** + * @return string + */ + public function getField() + { + return $this->privateField; + } + + /** + * @return string + */ + public function getPublishDate() + { + return $this->publishDate; + } + + /** + * @param string $publishDate + */ + public function setPublishDate($publishDate) + { + $this->publishDate = $publishDate; + } + + /** + * @return array + */ + public function getComplexDataType() + { + return $this->complexDataType; + } + + /** + * @param string $complexDataType + */ + public function setComplexDataType($complexDataType) + { + $this->complexDataType = $complexDataType; + } + + public function getComplexData() + { + return json_decode($this->complexDataType, true); + } +} + diff --git a/Tests/Fixtures/ValidTestEntityAllCores.php b/Tests/Fixtures/ValidTestEntityAllCores.php new file mode 100644 index 00000000..8301ded2 --- /dev/null +++ b/Tests/Fixtures/ValidTestEntityAllCores.php @@ -0,0 +1,192 @@ +id; + } + + /** + * @param int $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string $text + */ + public function getText() + { + return $this->text; + } + + /** + * @return string $title + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @param string $costomField + */ + public function setCostomField($costomField) + { + $this->costomField = $costomField; + } + + /** + * @return string + */ + public function getCostomField() + { + return $this->costomField; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->created_at; + } + + /** + * @param \DateTime $created_at + */ + public function setCreatedAt($created_at) + { + $this->created_at = $created_at; + } + + /** + * @return ValidTestEntity[] + */ + public function getPosts() + { + return $this->posts; + } + + /** + * @param ValidTestEntity[] $posts + */ + public function setPosts($posts) + { + $this->posts = $posts; + } + + /** + * @param string $field + */ + public function setField($field) + { + $this->privateField = $field; + } + + /** + * @return string + */ + public function getField() + { + return $this->privateField; + } + + /** + * @return string + */ + public function getPublishDate() + { + return $this->publishDate; + } + + /** + * @param string $publishDate + */ + public function setPublishDate($publishDate) + { + $this->publishDate = $publishDate; + } +} + diff --git a/Tests/Doctrine/Annotation/Entities/ValidTestEntityFiltered.php b/Tests/Fixtures/ValidTestEntityFiltered.php similarity index 76% rename from Tests/Doctrine/Annotation/Entities/ValidTestEntityFiltered.php rename to Tests/Fixtures/ValidTestEntityFiltered.php index 9e5ffc2f..67106dfb 100644 --- a/Tests/Doctrine/Annotation/Entities/ValidTestEntityFiltered.php +++ b/Tests/Fixtures/ValidTestEntityFiltered.php @@ -1,15 +1,22 @@ id; + } + + public function setId($id) + { + $this->id = $id; + } + + /** + * @param string $costomField + */ + public function setCostomField($costomField) + { + $this->costomField = $costomField; + } + + /** + * @return string + */ + public function getCostomField() + { + return $this->costomField; + } + + /** + * @return ArrayCollection + */ + public function getCollection() + { + return $this->collection; + } + + /** + * @param ArrayCollection $collection + */ + public function setCollection($collection) + { + $this->collection = $collection; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->created_at; + } + + /** + * @param \DateTime $created_at + */ + public function setCreatedAt($created_at) + { + $this->created_at = $created_at; + } + + /** + * @return ArrayCollection + */ + public function getCollectionNoGetter() + { + return $this->collectionNoGetter; + } + + /** + * @param ArrayCollection $collectionNoGetter + */ + public function setCollectionNoGetter(ArrayCollection $collectionNoGetter) + { + $this->collectionNoGetter = $collectionNoGetter; + } +} + diff --git a/Tests/Doctrine/Annotation/Entities/ValidTestEntityWithInvalidBoost.php b/Tests/Fixtures/ValidTestEntityWithInvalidBoost.php similarity index 74% rename from Tests/Doctrine/Annotation/Entities/ValidTestEntityWithInvalidBoost.php rename to Tests/Fixtures/ValidTestEntityWithInvalidBoost.php index c14fcfea..21b3093b 100644 --- a/Tests/Doctrine/Annotation/Entities/ValidTestEntityWithInvalidBoost.php +++ b/Tests/Fixtures/ValidTestEntityWithInvalidBoost.php @@ -1,10 +1,10 @@ id; @@ -54,55 +68,87 @@ public function setId($id) } /** - * @return the $text + * @param string $costomField */ - public function getText() + public function setCostomField($costomField) { - return $this->text; + $this->costomField = $costomField; } /** - * @return the $title + * @return string */ - public function getTitle() + public function getCostomField() { - return $this->title; + return $this->costomField; } /** - * @param \FS\BlogBundle\Tests\Solr\Doctrine\Mapper\text $text + * @return object */ - public function setText($text) + public function getRelation() { - $this->text = $text; + return $this->relation; } /** - * @param \FS\BlogBundle\Tests\Solr\Doctrine\Mapper\text $title + * @param object $relation */ - public function setTitle($title) + public function setRelation($relation) { - $this->title = $title; + $this->relation = $relation; } /** - * @param string $costomField + * @return object */ - public function setCostomField($costomField) + public function getPosts() { - $this->costomField = $costomField; + return $this->posts; + } + + /** + * @param object $posts + */ + public function setPosts($posts) + { + $this->posts = $posts; } /** * @return string */ - public function getCostomField() + public function getText() { - return $this->costomField; + return $this->text; + } + + /** + * @param string $text + */ + public function setText($text) + { + $this->text = $text; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; } /** - * @return \FS\SolrBundle\Tests\Doctrine\Mapper\date + * @return \DateTime */ public function getCreatedAt() { @@ -110,7 +156,7 @@ public function getCreatedAt() } /** - * @param \FS\SolrBundle\Tests\Doctrine\Mapper\date $created_at + * @param \DateTime $created_at */ public function setCreatedAt($created_at) { diff --git a/Tests/Integration/Bootstrap/CrudFeatureContext.php b/Tests/Integration/Bootstrap/CrudFeatureContext.php deleted file mode 100644 index 5ce049ed..00000000 --- a/Tests/Integration/Bootstrap/CrudFeatureContext.php +++ /dev/null @@ -1,110 +0,0 @@ -solr = $this->getMainContext()->getSolrInstance(); - - $this->entity = new \FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity(); - $this->entity->setId(\FS\SolrBundle\Tests\Util\EntityIdentifier::generate()); - $this->entity->setText('a Text'); - } - - /** - * @When /^I add this entity to Solr$/ - */ - public function iAddThisEntityToSolr() - { - $this->solr->addDocument($this->entity); - } - - /** - * @Then /^should no error occurre$/ - */ - public function shouldNoErrorOccurre() - { - $eventDispatcher = $this->getMainContext()->getEventDispatcher(); - - if ($eventDispatcher->errorsOccurred()) { - throw new RuntimeException(sprintf('error occurred while indexing: %s', $eventDispatcher->getOccurredErrors())); - } - - $this->getMainContext()->assertInsertSuccessful(); - } - - /** - * @When /^I update one attribute$/ - */ - public function iUpdateOneAttribute() - { - $this->entity->setText('text has changed'); - } - - /** - * @Then /^the index should be updated$/ - */ - public function theIndexShouldBeUpdated() - { - $client = $this->getMainContext()->getSolrClient(); - $entityId = $this->entity->getId(); - - $query = $client->createSelect(); - $query->setQuery(sprintf('id:%s', $entityId)); - $resultset = $client->select($query); - - if ($resultset->getNumFound() == 0) { - throw new RuntimeException(sprintf('could not find document with id %s after update', $entityId)); - } - - foreach ($resultset as $document) { - $changedFieldValue = $document['text_t']; - - if ($changedFieldValue != $this->entity->getText()) { - throw new RuntimeException(sprintf('updated entity with id %s was not updated in solr', $entityId)); - } - } - } - - /** - * @When /^I delete the entity$/ - */ - public function iDeleteTheEntity() - { - $this->solr->removeDocument($this->entity); - } - - /** - * @Then /^I should not find the entity in Solr$/ - */ - public function iShouldNotFindTheEntityInSolr() - { - $client = $this->getMainContext()->getSolrClient(); - $entityId = $this->entity->getId(); - - $query = $client->createSelect(); - $query->setQuery(sprintf('id:%s', $entityId)); - $resultset = $client->select($query); - - if ($resultset->getNumFound() > 0) { - throw new \RuntimeException(sprintf('document with id %s should not found in the index', $entityId)); - } - } - -} \ No newline at end of file diff --git a/Tests/Integration/Bootstrap/FeatureContext.php b/Tests/Integration/Bootstrap/FeatureContext.php deleted file mode 100644 index d7c48d05..00000000 --- a/Tests/Integration/Bootstrap/FeatureContext.php +++ /dev/null @@ -1,158 +0,0 @@ -useContext('crud', new CrudFeatureContext()); - - $this->eventDispatcher = new \FS\SolrBundle\Tests\Integration\EventDispatcherFake(); - } - - /** - * @return \FS\SolrBundle\Tests\Integration\EventDispatcherFake - */ - public function getEventDispatcher() - { - return $this->eventDispatcher; - } - - /** - * @return \Solarium\Client - */ - public function getSolrClient() - { - return $this->solrClient; - } - - /** - * @return \FS\SolrBundle\Solr - */ - public function getSolrInstance() - { - \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader('class_exists'); - \Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver::registerAnnotationClasses(); - - $this->solrClient = $this->setupSolrClient(); - $factory = $this->setupCommandFactory(); - $metaFactory = $this->setupMetaInformationFactory(); - $entityMapper = $this->setupEntityMapper(); - - $solr = new \FS\SolrBundle\Solr( - $this->solrClient, - $factory, - $this->eventDispatcher, - $metaFactory, - $entityMapper - ); - - return $solr; - } - - private function setupEntityMapper() - { - $registry = new \FS\SolrBundle\Tests\Integration\DoctrineRegistryFake(); - - $entityMapper = new \FS\SolrBundle\Doctrine\Mapper\EntityMapper( - new \FS\SolrBundle\Doctrine\Hydration\DoctrineHydrator( - $registry, - new \FS\SolrBundle\Doctrine\Hydration\ValueHydrator() - ), - new \FS\SolrBundle\Doctrine\Hydration\IndexHydrator( - new \FS\SolrBundle\Doctrine\Hydration\ValueHydrator() - ) - ); - - return $entityMapper; - } - - /** - * @return \FS\SolrBundle\Doctrine\Mapper\Mapping\CommandFactory - */ - private function setupCommandFactory() - { - $factory = new \FS\SolrBundle\Doctrine\Mapper\Mapping\CommandFactory(); - $factory->add(new \FS\SolrBundle\Doctrine\Mapper\Mapping\MapAllFieldsCommand(), 'all'); - $factory->add(new \FS\SolrBundle\Doctrine\Mapper\Mapping\MapIdentifierCommand(), 'identifier'); - - return $factory; - } - - /** - * @return \FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory - */ - private function setupMetaInformationFactory() - { - $ormConfiguration = new Doctrine\ORM\Configuration(); - $ormConfiguration->addEntityNamespace('FSTest:ValidTestEntity', 'FS\SolrBundle\Tests\Doctrine\Mapper'); - - $knowNamespaces = new \FS\SolrBundle\Doctrine\ClassnameResolver\KnownNamespaceAliases(); - $knowNamespaces->addEntityNamespaces($ormConfiguration); - - $classnameResolver = new \FS\SolrBundle\Doctrine\ClassnameResolver\ClassnameResolver($knowNamespaces); - - $metaFactory = new \FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory(); - $metaFactory->setClassnameResolver( - $classnameResolver - ); - - return $metaFactory; - } - - /** - * @return \Solarium\Client - */ - private function setupSolrClient() - { - $config = array( - 'default' => array( - 'host' => 'localhost', - 'port' => 8983, - 'path' => '/solr/', - ) - ); - - $builder = new \FS\SolrBundle\Builder\SolrBuilder($config); - $solrClient = $builder->build(); - - return $solrClient; - } - - public function assertInsertSuccessful() - { - if (!$this->eventDispatcher->eventOccurred(\FS\SolrBundle\Event\Events::POST_INSERT) || - !$this->eventDispatcher->eventOccurred(\FS\SolrBundle\Event\Events::PRE_INSERT)) { - throw new RuntimeException('Insert was not successful'); - } - } -} diff --git a/Tests/Integration/Entity/Category.php b/Tests/Integration/Entity/Category.php new file mode 100644 index 00000000..27be6226 --- /dev/null +++ b/Tests/Integration/Entity/Category.php @@ -0,0 +1,122 @@ +posts = new ArrayCollection(); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * @return Post[] + */ + public function getPosts() + { + return $this->posts; + } + + /** + * @param Post[] $posts + */ + public function setPosts($posts) + { + $this->posts = $posts; + } + + public function addPost($post) + { + $this->posts->add($post); + } + + /** + * @param string $info + */ + public function setInfo($info) + { + $this->info = $info; + } + +} \ No newline at end of file diff --git a/Tests/Integration/Entity/Post.php b/Tests/Integration/Entity/Post.php new file mode 100644 index 00000000..2c5623de --- /dev/null +++ b/Tests/Integration/Entity/Post.php @@ -0,0 +1,294 @@ +intField = 3; + $this->tags = new ArrayCollection(); + } + + public function setId($id) + { + $this->id = $id; + } + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Set title + * + * @param string $title + * @return Post + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * Get title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set text + * + * @param string $text + * @return Post + */ + public function setText($text) + { + $this->text = $text; + + return $this; + } + + /** + * Get text + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Set created + * + * @param \DateTime $created + * @return Post + */ + public function setCreated($created) + { + $this->created = $created; + + return $this; + } + + /** + * Get created + * + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + public function selectCore() + { + if ($this->lang == 'en') { + return 'core0'; + } + + return 'core1'; + } + + /** + * @return Category + */ + public function getCategory() + { + return $this->category; + } + + /** + * @param Category $category + */ + public function setCategory($category) + { + $this->category = $category; + } + + /** + * @return Tag[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * @param Tag[] $tags + */ + public function setTags($tags) + { + $this->tags = $tags; + } + + /** + * @return string + */ + public function getSlug() + { + return $this->slug; + } + + /** + * @param string $slug + */ + public function setSlug($slug) + { + $this->slug = $slug; + } + + /** + * @return array + */ + public function getMultivalues() + { + return $this->multivalues; + } + + /** + * @param array $multivalues + */ + public function setMultivalues($multivalues) + { + $this->multivalues = $multivalues; + } + + /** + * @return bool + */ + public function isIsParent() : bool + { + return $this->isParent; + } + + /** + * @param bool $isParent + */ + public function setIsParent(bool $isParent) + { + $this->isParent = $isParent; + } +} diff --git a/Tests/Integration/Entity/Tag.php b/Tests/Integration/Entity/Tag.php new file mode 100644 index 00000000..250a2784 --- /dev/null +++ b/Tests/Integration/Entity/Tag.php @@ -0,0 +1,114 @@ +name = $name; + } + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + */ + public function setId(int $id) + { + $this->id = $id; + } + + /** + * Set name + * + * @param string $name + * + * @return Tag + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Post + */ + public function getPost() + { + return $this->post; + } + + /** + * @param Post $post + */ + public function setPost($post) + { + $this->post = $post; + } + + +} + diff --git a/Tests/Integration/EventDispatcherFake.php b/Tests/Integration/EventDispatcherFake.php index 12827a50..68e1a282 100644 --- a/Tests/Integration/EventDispatcherFake.php +++ b/Tests/Integration/EventDispatcherFake.php @@ -20,13 +20,18 @@ class EventDispatcherFake implements EventDispatcherInterface */ private $events = array(); + public function getEvents() + { + return $this->events; + } + /** * Dispatches an event to all registered listeners. * * @param string $eventName The name of the event to dispatch. The name of * the event is the name of the method that is * invoked on listeners. - * @param Event $event The event to pass to the event handlers/listeners. + * @param Event $event The event to pass to the event handlers/listeners. * If not supplied, an empty Event instance is created. * * @return Event @@ -88,9 +93,9 @@ public function eventOccurred($eventName) /** * Adds an event listener that listens on the specified events. * - * @param string $eventName The event to listen on + * @param string $eventName The event to listen on * @param callable $listener The listener - * @param integer $priority The higher this value, the earlier an event + * @param integer $priority The higher this value, the earlier an event * listener will be triggered in the chain (defaults to 0) * * @api @@ -119,7 +124,7 @@ public function addSubscriber(EventSubscriberInterface $subscriber) * Removes an event listener from the specified events. * * @param string|array $eventName The event(s) to remove a listener from - * @param callable $listener The listener to remove + * @param callable $listener The listener to remove */ public function removeListener($eventName, $listener) { @@ -160,4 +165,19 @@ public function hasListeners($eventName = null) // TODO: Implement hasListeners() method. } + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener) + { + // TODO: Implement getListenerPriority() method. + } + } \ No newline at end of file diff --git a/Tests/Integration/Features/crud.feature b/Tests/Integration/Features/crud.feature deleted file mode 100644 index f576bad9..00000000 --- a/Tests/Integration/Features/crud.feature +++ /dev/null @@ -1,21 +0,0 @@ -Feature: I can index entities to a Solr instance - - Scenario: I index one entity - Given I have a Doctrine entity - When I add this entity to Solr - Then should no error occurre - - Scenario: I can update one entity - Given I have a Doctrine entity - When I add this entity to Solr - Then should no error occurre - When I update one attribute - And I add this entity to Solr - Then the index should be updated - - Scenario: I can delete a entity - Given I have a Doctrine entity - When I add this entity to Solr - Then should no error occurre - When I delete the entity - Then I should not find the entity in Solr \ No newline at end of file diff --git a/Tests/Integration/IndexingTest.php b/Tests/Integration/IndexingTest.php new file mode 100644 index 00000000..45ec84af --- /dev/null +++ b/Tests/Integration/IndexingTest.php @@ -0,0 +1,252 @@ +eventDispatcher = new EventDispatcherFake(); + $this->client = $this->setupSolrClient(); + + try { + $this->client->ping(new Query()); + } catch (\Exception $e) { + $this->markTestSkipped('solr is not running on localhost:8983'); + return; + } + + $metainformationFactory = new MetaInformationFactory(new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader())); + $logger = $this->createMock(LoggerInterface::class); + + $this->solr = new Solr( + $this->client, + $this->eventDispatcher, + $metainformationFactory, + new EntityMapper( + new DoctrineHydrator(new ValueHydrator()), + new IndexHydrator(new ValueHydrator()), + $metainformationFactory + ) + ); + + $this->solr->clearIndex(); + + $this->doctrineSubscriber = new EntityIndexerSubscriber($this->solr, $metainformationFactory, $logger); + } + + /** + * Solarium Client with two cores (core0, core1) + * + * @return Client + */ + private function setupSolrClient() + { + $config = array( + 'core0' => array( + 'host' => 'localhost', + 'port' => 8983, + 'path' => '/solr/core0', + ) + ); + + $builder = new SolariumClientBuilder($config, $this->eventDispatcher); + $solrClient = $builder->build(); + + return $solrClient; + } + + /** + * @test + */ + public function indexSingleEntity() + { + $currentDate = date('c') . 'Z'; + + $post = new Post(); + $post->setId(1); + $post->setTitle('indexSingleEntity'); + $post->setCreated($currentDate); + $post->setMultivalues(['foo', 'bar']); + + $objectManager = $this->createMock(EntityManagerInterface::class); + + $lifecycleEventArgs = new LifecycleEventArgs($post, $objectManager); + + $this->doctrineSubscriber->postPersist($lifecycleEventArgs); + + $this->doctrineSubscriber->postFlush(new PostFlushEventArgs($objectManager)); + + $events = $this->eventDispatcher->getEvents(); + + $expectedRequest = 'post_1indexSingleEntity' . $currentDate . 'foobar'; + + $this->assertEquals($expectedRequest, $events['solarium.core.preExecuteRequest']->getRequest()->getRawData()); + } + + /** + * @test + */ + public function deleteEntityWithNested() + { + $post = new Post(); + $post->setId(1); + $post->setTitle('deleteEntityWithOneToOne'); + + $category = new Category(); + $category->setId(1); + $category->setTitle('deleteEntityWithOneToOne category'); + + $post->setCategory($category); + + $objectManager = $this->createMock(EntityManagerInterface::class); + + $this->doctrineSubscriber->postPersist(new LifecycleEventArgs($post, $objectManager)); + $this->doctrineSubscriber->postFlush(new PostFlushEventArgs($objectManager)); + + $this->assertEntityExists('deleteEntityWithOneToOne', 'deleteEntityWithOneToOne category'); + + $this->doctrineSubscriber->preRemove(new LifecycleEventArgs($category, $objectManager)); + $this->doctrineSubscriber->preRemove(new LifecycleEventArgs($post, $objectManager)); + $this->doctrineSubscriber->postFlush(new PostFlushEventArgs($objectManager)); + + $this->assertEntityNotExists('deleteEntityWithOneToOne', 'deleteEntityWithOneToOne category'); + } + + private function assertEntityExists($postName, $categoryName) + { + $query = $this->client->createSelect(); + $query->setQuery('title_s:' . $postName); + + $result = $this->client->execute($query); + + $this->assertEquals(1, $result->getData()['response']['numFound']); + + $query->setQuery('title_s:"'. $categoryName .'"'); + + $result = $this->client->execute($query); + + $this->assertEquals(1, $result->getData()['response']['numFound']); + } + + private function assertEntityNotExists($postName, $categoryName) + { + $query = $this->client->createSelect(); + $query->setQuery('title_s:' . $postName); + + $result = $this->client->execute($query); + + $this->assertEquals(0, $result->getData()['response']['numFound']); + + $query->setQuery('title_s:"'. $categoryName .'"'); + + $result = $this->client->execute($query); + + $this->assertEquals(0, $result->getData()['response']['numFound']); + } + + /** + * @test + */ + public function indexEntityWithOneToOne() + { + $post = new Post(); + $post->setId(1); + $post->setTitle('indexEntityWithOneToOne'); + + $category = new Category(); + $category->setId(1); + $category->setTitle('indexEntityWithOneToOne category'); + + $post->setCategory($category); + + $objectManager = $this->createMock(EntityManagerInterface::class); + + $lifecycleEventArgs = new LifecycleEventArgs($post, $objectManager); + + $this->doctrineSubscriber->postPersist($lifecycleEventArgs); + + $this->doctrineSubscriber->postFlush(new PostFlushEventArgs($objectManager)); + + $events = $this->eventDispatcher->getEvents(); + + $expectedRequest = 'post_1indexEntityWithOneToOnecategory_1indexEntityWithOneToOne category'; + + $this->assertEquals($expectedRequest, $events['solarium.core.preExecuteRequest']->getRequest()->getRawData()); + + $this->assertEntityExists('indexEntityWithOneToOne', 'indexEntityWithOneToOne category'); + } + + /** + * @test + */ + public function indexEntityWithOneToMany() + { + $post = new Post(); + $post->setId(1); + $post->setTitle('indexEntityWithOneToMany'); + + $tag1 = new Tag('tag indexEntityWithOneToMany 1'); + $tag1->setId(1); + $tag2 = new Tag('tag indexEntityWithOneToMany 2'); + $tag2->setId(2); + + $post->setTags([$tag1, $tag2]); + + $objectManager = $this->createMock(EntityManagerInterface::class); + + + $this->doctrineSubscriber->postPersist(new LifecycleEventArgs($tag1, $objectManager)); + $this->doctrineSubscriber->postPersist(new LifecycleEventArgs($tag2, $objectManager)); + $this->doctrineSubscriber->postPersist(new LifecycleEventArgs($post, $objectManager)); + + $this->doctrineSubscriber->postFlush(new PostFlushEventArgs($objectManager)); + + $events = $this->eventDispatcher->getEvents(); + + $expectedRequest = 'post_1indexEntityWithOneToManytag_1tag indexEntityWithOneToMany 1tag_2tag indexEntityWithOneToMany 2'; + + $this->assertEquals($expectedRequest, $events['solarium.core.preExecuteRequest']->getRequest()->getRawData()); + } +} \ No newline at end of file diff --git a/Tests/Integration/SolariumClientFake.php b/Tests/Integration/SolariumClientFake.php new file mode 100644 index 00000000..44d07b5b --- /dev/null +++ b/Tests/Integration/SolariumClientFake.php @@ -0,0 +1,10 @@ +getMock('Solarium\QueryType\Update\Query\Query', array(), array(), '', false); + $updateQuery = $this->createMock(Query::class); $updateQuery->expects($this->once()) ->method('addDocument'); @@ -24,6 +32,8 @@ private function assertUpdateQueryExecuted() ->expects($this->once()) ->method('createUpdate') ->will($this->returnValue($updateQuery)); + + return $updateQuery; } /** @@ -31,13 +41,11 @@ private function assertUpdateQueryExecuted() */ public function addDocumentToAllCores() { - $this->assertUpdateQueryExecuted(); + $updateQuery = $this->assertUpdateQueryExecuted(); $this->eventDispatcher->expects($this->any()) ->method('dispatch'); - $this->mapOneDocument(); - $this->solrClientFake->expects($this->once()) ->method('getEndpoints') ->will($this->returnValue(array( @@ -45,15 +53,20 @@ public function addDocumentToAllCores() 'core1' => array() ))); - $this->solrClientFake->expects($this->exactly(2)) - ->method('update'); + $this->solrClientFake->expects($this->at(2)) + ->method('update') + ->with($updateQuery, 'core0'); - $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $metaInformation->setIndex('*'); - $this->setupMetaFactoryLoadOneCompleteInformation($metaInformation); + $this->solrClientFake->expects($this->at(3)) + ->method('update') + ->with($updateQuery, 'core1'); + + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->addDocument(new ValidTestEntity()); + $solr = new Solr($this->solrClientFake, $this->eventDispatcher, $this->metaFactory, $this->mapper); + $solr->addDocument(new ValidTestEntityAllCores()); } /** @@ -61,13 +74,11 @@ public function addDocumentToAllCores() */ public function updateDocumentInAllCores() { - $this->assertUpdateQueryExecuted(); + $updateQuery = $this->assertUpdateQueryExecuted(); $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch'); - $this->mapOneDocument(); - $this->solrClientFake->expects($this->once()) ->method('getEndpoints') ->will($this->returnValue(array( @@ -75,16 +86,20 @@ public function updateDocumentInAllCores() 'core1' => array() ))); - $this->solrClientFake->expects($this->exactly(2)) - ->method('update'); + $this->solrClientFake->expects($this->at(2)) + ->method('update') + ->with($updateQuery, 'core0'); + $this->solrClientFake->expects($this->at(3)) + ->method('update') + ->with($updateQuery, 'core1'); - $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $metaInformation->setIndex('*'); - $this->setupMetaFactoryLoadOneCompleteInformation($metaInformation); + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->updateDocument(new ValidTestEntity()); + $solr = new Solr($this->solrClientFake, $this->eventDispatcher, $this->metaFactory, $this->mapper); + $solr->updateDocument(new ValidTestEntityAllCores()); } /** @@ -92,10 +107,6 @@ public function updateDocumentInAllCores() */ public function removeDocumentFromAllCores() { - $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $metaInformation->setIndex('*'); - $this->setupMetaFactoryLoadOneCompleteInformation($metaInformation); - $this->mapper->expects($this->once()) ->method('toDocument') ->will($this->returnValue(new DocumentStub())); @@ -107,7 +118,7 @@ public function removeDocumentFromAllCores() 'core1' => array() ))); - $deleteQuery = $this->getMock('Solarium\QueryType\Update\Query\Query', array(), array(), '', false); + $deleteQuery = $this->createMock(Query::class); $deleteQuery->expects($this->once()) ->method('addDeleteQuery') ->with($this->isType('string')); @@ -123,8 +134,8 @@ public function removeDocumentFromAllCores() $this->solrClientFake->expects($this->exactly(2)) ->method('update'); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->removeDocument(new ValidTestEntity()); + $solr = new Solr($this->solrClientFake, $this->eventDispatcher, $this->metaFactory, $this->mapper); + $solr->removeDocument(new ValidTestEntityAllCores()); } } \ No newline at end of file diff --git a/Tests/Query/FindByDocumentNameQueryTest.php b/Tests/Query/FindByDocumentNameQueryTest.php index 4833b028..9a511333 100644 --- a/Tests/Query/FindByDocumentNameQueryTest.php +++ b/Tests/Query/FindByDocumentNameQueryTest.php @@ -16,28 +16,26 @@ class FindByDocumentNameQueryTest extends \PHPUnit_Framework_TestCase public function testGetQuery_SearchInAllFields() { $document = new Document(); - $document->addField('document_name_s', 'validtestentity'); + $document->addField('id', 'validtestentity_1'); $query = new FindByDocumentNameQuery(); + $query->setDocumentName('validtestentity'); $query->setDocument($document); - $queryString = $query->getQuery(); - - $this->assertEquals('document_name_s:validtestentity', $queryString, 'filter query'); + $this->assertEquals('*:*', $query->getQuery(), 'filter query'); + $this->assertEquals('id:validtestentity_*', $query->getFilterQuery('id')->getQuery()); } + /** + * @expectedException FS\SolrBundle\Query\Exception\QueryException + * @expectedExceptionMessage documentName should not be null + */ public function testGetQuery_DocumentnameMissing() { $query = new FindByDocumentNameQuery(); $query->setDocument(new Document()); - try { - $query->getQuery(); - - $this->fail('an exception should be thrown'); - } catch (\RuntimeException $e) { - $this->assertTrue(true); - } + $query->getQuery(); } } diff --git a/Tests/Query/FindByIdentifierQueryTest.php b/Tests/Query/FindByIdentifierQueryTest.php index 3cdfe7bf..35f5920d 100644 --- a/Tests/Query/FindByIdentifierQueryTest.php +++ b/Tests/Query/FindByIdentifierQueryTest.php @@ -14,46 +14,25 @@ class FindByIdentifierQueryTest extends \PHPUnit_Framework_TestCase public function testGetQuery_SearchInAllFields() { $document = new Document(); - $document->addField('id', '1'); - $document->addField('document_name_s', 'validtestentity'); + $document->setKey('id', 'validtestentity_1'); - $expectedQuery = 'id:1 AND document_name_s:validtestentity'; $query = new FindByIdentifierQuery(); + $query->setDocumentKey('validtestentity_1'); $query->setDocument($document); - $queryString = $query->getQuery(); - - $this->assertEquals($expectedQuery, $queryString); - } - - public function testGetQuery_DocumentNameMissing() - { - $document = new Document(); - $document->addField('id', '1'); - - $query = new FindByIdentifierQuery(); - $query->setDocument($document); - - try { - $query->getQuery(); - - $this->fail('an exception should be thrown'); - } catch (\RuntimeException $e) { - $this->assertEquals('documentName should not be null', $e->getMessage()); - } + $this->assertEquals('*:*', $query->getQuery()); + $this->assertEquals('id:validtestentity_1', $query->getFilterQuery('id')->getQuery()); } + /** + * @expectedException FS\SolrBundle\Query\Exception\QueryException + * @expectedExceptionMessage id should not be null + */ public function testGetQuery_IdMissing() { $query = new FindByIdentifierQuery(); $query->setDocument(new Document()); - try { - $query->getQuery(); - - $this->fail('an exception should be thrown'); - } catch (\RuntimeException $e) { - $this->assertEquals('id should not be null', $e->getMessage()); - } + $query->getQuery(); } } diff --git a/Tests/Query/QueryBuilderTest.php b/Tests/Query/QueryBuilderTest.php new file mode 100644 index 00000000..594cb1c7 --- /dev/null +++ b/Tests/Query/QueryBuilderTest.php @@ -0,0 +1,172 @@ +solr = $this->createMock(SolrInterface::class); + } + + /** + * @test + */ + public function christmasReadme() + { + $metaInformation = $this->setupMetainformation(); + + $builder = new QueryBuilder($this->solr, $metaInformation); + + $nearNorthPole = $builder->where('position')->nearCircle(38.116181, -86.929463, 100.5); + self::assertEquals("{!bbox pt=38.116181,-86.929463 sfield=position_s d=100.5}", $nearNorthPole->getQuery()->getCustomQuery()); + + $builder = new QueryBuilder($this->solr, $metaInformation); + $santaClaus = $builder->where('santa-name')->contains(['Noel', 'Claus', 'Natale', 'Baba', 'Nicolas']) + ->andWhere('santa-beard-exists')->is(true) + ->andWhere('santa-beard-lenght')->between(5.5, 10.0) + ->andWhere('santa-beard-color')->startsWith('whi')->endsWith('te') + ->andWhere($nearNorthPole); + + self::assertEquals("santa-name_ss:(*Noel* *Claus* *Natale* *Baba* *Nicolas*) AND santa-beard-exists_b:true AND santa-beard-lenght_f:[5.5 TO 10] AND santa-beard-color_s:(whi* *te) AND {!bbox pt=38.116181,-86.929463 sfield=position_s d=100.5}", $santaClaus->getQuery()->getCustomQuery()); + + $builder = new QueryBuilder($this->solr, $metaInformation); + $goodPeople = $builder->where('good-actions')->greaterThanEqual(10) + ->orWhere('bad-actions')->lessThanEqual(5); + + self::assertEquals('good-actions_i:[10 TO *] OR bad-actions_i:[* TO 5]', $goodPeople->getQuery()->getCustomQuery()); + + $builder = new QueryBuilder($this->solr, $metaInformation); + $gifts = $builder->where('gift-name')->sloppy('LED TV GoPro Oculus Tablet Laptop', 2) + ->andWhere('gift-type')->fuzzy('information', 0.4)->startsWith('tech') + ->andWhere('__query__')->expression('{!dismax qf=myfield}how now brown cow'); + + self::assertEquals('gift-name_s:"LED TV GoPro Oculus Tablet Laptop"~2 AND gift-type_s:(information~0.4 tech*) AND __query___s:{!dismax qf=myfield}how now brown cow', $gifts->getQuery()->getCustomQuery()); + + $builder1 = new QueryBuilder($this->solr, $metaInformation); + + $builder2 = new QueryBuilder($this->solr, $metaInformation); + + $christmas = new \DateTime('2016-12-25'); + $contributors = ['Christoph', 'Philipp', 'Francisco', 'Fabio']; + $giftReceivers = $builder1->where('gift-received')->is(null) + ->andWhere('chimney')->isNotNull() + ->andWhere('date')->is($christmas)->greaterThanEqual(new \Datetime('1970-01-01')) + ->andWhere($santaClaus) + ->andWhere($gifts) + ->andWhere( + $builder2->where('name')->in($contributors)->boost(2.0) + ->orWhere($goodPeople) + ); + + self::assertEquals("-gift-received_s:[* TO *] AND chimney_s:[* TO *] AND date_dt:(2016\\-12\\-25T00\\:00\\:00Z [1970\\-01\\-01T00\\:00\\:00Z TO *]) AND (santa-name_ss:(*Noel* *Claus* *Natale* *Baba* *Nicolas*) AND santa-beard-exists_b:true AND santa-beard-lenght_f:[5.5 TO 10] AND santa-beard-color_s:(whi* *te) AND {!bbox pt=38.116181,-86.929463 sfield=position_s d=100.5}) AND (gift-name_s:\"LED TV GoPro Oculus Tablet Laptop\"~2 AND gift-type_s:(information~0.4 tech*) AND __query___s:{!dismax qf=myfield}how now brown cow) AND (name_s:(Christoph Philipp Francisco Fabio)^2.0 OR (good-actions_i:[10 TO *] OR bad-actions_i:[* TO 5]))", $giftReceivers->getQuery()->getCustomQuery()); + } + + /** + * @test + */ + public function doNotAddIdFieldTwice() + { + $builder = new QueryBuilder($this->solr, $this->setupMetainformation()); + + $query = $builder + ->where('santa-beard-exists')->is(true) + ->andWhere('santa-beard-lenght')->between(5.5, 10.0) + ->andWhere('santa-beard-color')->startsWith('whi')->endsWith('te') + ->andWhere('id')->is('post_1') + ->getQuery()->getQuery(); + + $this->assertEquals('santa-beard-exists_b:true AND santa-beard-lenght_f:[5.5 TO 10] AND santa-beard-color_s:(whi* *te) AND id:post_1', $query); + } + + /** + * @test + * @expectedException \FS\SolrBundle\Doctrine\Mapper\SolrMappingException + * @expectedExceptionMessage $fieldName must not be empty + */ + public function setEmpty() + { + $builder = new QueryBuilder($this->solr, $this->setupMetainformation()); + $query = $builder + ->where('') + ->getQuery()->getQuery(); + } + + /** + * @return MetaInformation + */ + private function setupMetainformation() + { + $metaInformation = new MetaInformation(); + + $field1 = new Field(array()); + $field1->name = 'position'; + $field1->type = 'string'; + + $field2 = new Field(array()); + $field2->name = 'santa-beard-exists'; + $field2->type = 'boolean'; + + $field3 = new Field(array()); + $field3->name = 'santa-beard-lenght'; + $field3->type = 'float'; + + $field4 = new Field(array()); + $field4->name = 'santa-beard-color'; + $field4->type = 'string'; + + $field5 = new Field(array()); + $field5->name = 'good-actions'; + $field5->type = 'integer'; + + $field6 = new Field(array()); + $field6->name = 'gift-name'; + $field6->type = 'string'; + + $field7 = new Field(array()); + $field7->name = 'gift-type'; + $field7->type = 'string'; + + $field8 = new Field(array()); + $field8->name = 'gift-received'; + $field8->type = 'string'; + + $field9 = new Field(array()); + $field9->name = 'chimney'; + $field9->type = 'string'; + + $field10 = new Field(array()); + $field10->name = 'date'; + $field10->type = 'datetime'; + + $field11 = new Field(array()); + $field11->name = 'santa-name'; + $field11->type = 'strings'; + + $field12 = new Field(array()); + $field12->name = 'bad-actions'; + $field12->type = 'integer'; + + $field13 = new Field(array()); + $field13->name = '__query__'; + $field13->type = 'string'; + + $field14 = new Field(array()); + $field14->name = 'name'; + $field14->type = 'string'; + + $field15 = new Field(array()); + $field15->name = 'id'; + + $metaInformation->setFields(array($field1, $field2, $field3, $field4, $field5, $field6, $field7, $field8, $field9, $field10, $field11, $field12, $field13, $field14, $field15)); + + return $metaInformation; + } +} \ No newline at end of file diff --git a/Tests/Query/SolrQueryTest.php b/Tests/Query/SolrQueryTest.php index 9bc57926..e973c589 100644 --- a/Tests/Query/SolrQueryTest.php +++ b/Tests/Query/SolrQueryTest.php @@ -3,7 +3,12 @@ namespace FS\SolrBundle\Tests\Query; 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; use FS\SolrBundle\SolrQueryFacade; /** @@ -16,26 +21,37 @@ class SolrQueryTest extends \PHPUnit_Framework_TestCase private function getFieldMapping() { return array( + 'id' => 'id', 'title_s' => 'title', 'text_t' => 'text', 'created_at_dt' => 'created_at' ); } + /** + * @return SolrQuery + */ private function createQueryWithFieldMapping() { - $solr = $this->getMock('FS\SolrBundle\Solr', array(), array(), '', false); + $solr = $this->createMock(SolrInterface::class); + + $idField = new Id(array()); + $idField->name = 'id'; + + $metaInformation = new MetaInformation(); + $metaInformation->setDocumentName('post'); + $metaInformation->setIdentifier($idField); $solrQuery = new SolrQuery(); $solrQuery->setSolr($solr); $solrQuery->setMappedFields($this->getFieldMapping()); + $solrQuery->setMetaInformation($metaInformation); return $solrQuery; } /** - * - * @return \FS\SolrBundle\SolrQuery + * @return SolrQuery */ private function createQueryWithSearchTerms() { @@ -76,11 +92,12 @@ public function testAddField_OneFieldOfTwoNotMapped() public function testGetSolrQuery_QueryTermShouldCorrect() { - $expected = 'title_s:*foo* OR text_t:*bar*'; + $expected = 'title_s:foo OR text_t:bar'; $query = $this->createQueryWithSearchTerms(); $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); } @@ -97,56 +114,163 @@ public function testAddSearchTerm_AllFieldsAreMapped() $this->assertTrue(array_key_exists('text_t', $terms), 'text_t not in terms'); } - public function testAddSearchTerm_OneFieldOfTwoNotMapped() - { - $solrQuery = $this->createQueryWithFieldMapping(); - - $solrQuery->addSearchTerm('title', 'foo') - ->addSearchTerm('foo', 'bar'); - - $terms = $solrQuery->getSearchTerms(); - - $this->assertTrue(array_key_exists('title_s', $terms), 'title_s not in terms'); - $this->assertEquals(1, count($terms)); - } - + /** + * @expectedException \FS\SolrBundle\Query\Exception\UnknownFieldException + */ public function testAddSearchTerm_UnknownField() { $solrQuery = $this->createQueryWithFieldMapping(); $solrQuery->addSearchTerm('unknownfield', 'foo'); - - $terms = $solrQuery->getSearchTerms(); - - $this->assertEquals(0, count($terms)); } public function testGetQuery_TermsConcatWithOr() { - $expected = 'title_s:*foo* OR text_t:*bar*'; + $expected = 'title_s:foo OR text_t:bar'; $query = $this->createQueryWithSearchTerms(); $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); } public function testGetQuery_TermsConcatWithAnd() { - $expected = 'title_s:*foo* AND text_t:*bar*'; + $expected = 'title_s:foo AND text_t:bar'; $query = $this->createQueryWithSearchTerms(); $query->setUseAndOperator(true); $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); } public function testGetQuery_SearchInAllFields() { - $solrQuery = $this->createQueryWithFieldMapping(); - $solrQuery->queryAllFields('foo'); + $query = $this->createQueryWithFieldMapping(); + $query->queryAllFields('foo'); - $expected = 'title_s:*foo* OR text_t:*foo* OR created_at_dt:*foo*'; + $expected = 'title_s:foo OR text_t:foo OR created_at_dt:foo'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + public function testGetQuery_SurroundTermWithDoubleQuotes() + { + $query = $this->createQueryWithFieldMapping(); + $query->queryAllFields('foo 12'); + + $expected = 'title_s:"foo 12" OR text_t:"foo 12" OR created_at_dt:"foo 12"'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + public function testGetQuery_SurroundWildcardTermWithDoubleQuotes() + { + $query = $this->createQueryWithFieldMapping(); + $query->queryAllFields('foo 12'); + $query->setUseWildcard(true); + + $expected = 'title_s:"*foo 12*" OR text_t:"*foo 12*" OR created_at_dt:"*foo 12*"'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + public function testGetQuery_NoWildcard_Word() + { + $query = $this->createQueryWithFieldMapping(); + $query->setUseWildcard(false); + $query->addSearchTerm('title', 'a_word'); + + $expected = 'title_s:a_word'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + public function testGetQuery_NoSearchTerm() + { + $query = $this->createQueryWithFieldMapping(); + + $expected = '*:*'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + public function testGetQuery_CustomQuery() + { + $query = $this->createQueryWithFieldMapping(); + $query->setCustomQuery('title_s:[*:*]'); + + $expected = 'title_s:[*:*]'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + /** + * @test + */ + public function searchInSetMultipleValues() + { + $query = $this->createQueryWithFieldMapping(); + $query->addSearchTerm('title', array('value2', 'value1')); + + $expected = 'title_s:["value1" TO "value2"]'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + /** + * @test + */ + public function searchInSetSingleValues() + { + $query = $this->createQueryWithFieldMapping(); + $query->addSearchTerm('title', array('value #1')); + + $expected = 'title_s:"value #1"'; + + $this->assertEquals($expected, $query->getQuery()); + $this->assertEquals('id:post_*', $query->getFilterQuery('id')->getQuery()); + } + + /** + * @test + */ + public function doNotAddIdFieldTwice() + { + $expected = '*:*'; + + $query = $this->createQueryWithFieldMapping(); + $query->addSearchTerm('id', 'post_1'); + + $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*bar'); + $query->addSearchTerm('title', 'test post'); - $this->assertEquals($expected, $solrQuery->getQuery()); + $this->assertEquals('title_s:"test post" OR {!parent which="id:post_*"}name_s:test*bar', $query->getQuery()); } } diff --git a/Tests/Repository/RepositoryTest.php b/Tests/Repository/RepositoryTest.php index b270cd22..e58e0145 100644 --- a/Tests/Repository/RepositoryTest.php +++ b/Tests/Repository/RepositoryTest.php @@ -2,17 +2,43 @@ namespace FS\SolrBundle\Tests\Solr\Repository; +use FS\SolrBundle\Doctrine\Annotation\AnnotationReader; +use FS\SolrBundle\Doctrine\Hydration\HydrationModes; +use FS\SolrBundle\Doctrine\Mapper\EntityMapper; +use FS\SolrBundle\Doctrine\Mapper\EntityMapperInterface; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; +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; +use Solarium\Core\Query\Helper; use Solarium\QueryType\Update\Query\Document\Document; use FS\SolrBundle\Repository\Repository; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; /** * @group repository */ class RepositoryTest extends \PHPUnit_Framework_TestCase { + /** + * @var MetaTestInformationFactory + */ + 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() { @@ -20,92 +46,42 @@ public function testFind_DocumentIsKnown() $document->addField('id', 2); $document->addField('document_name_s', 'post'); - $metaFactory = $this->getMock( - 'FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory', - array(), - array(), - '', - false - ); - $metaFactory->expects($this->once()) - ->method('loadInformation') - ->will($this->returnValue(MetaTestInformationFactory::getMetaInformation())); - - $mapper = $this->getMock('FS\SolrBundle\Doctrine\Mapper\EntityMapper', array(), array(), '', false); - $mapper->expects($this->once()) - ->method('toDocument') - ->will($this->returnValue($document)); - - $solr = $this->getMock('FS\SolrBundle\Solr', array(), array(), '', false); - $solr->expects($this->exactly(2)) - ->method('getMapper') - ->will($this->returnValue($mapper)); - - $solr->expects($this->once()) - ->method('getCommandFactory') - ->will($this->returnValue(CommandFactoryStub::getFactoryWithAllMappingCommand())); - - $solr->expects($this->once()) - ->method('getMetaFactory') - ->will($this->returnValue($metaFactory)); + $metaInformation = MetaTestInformationFactory::getMetaInformation(); $entity = new ValidTestEntity(); - $solr->expects($this->once()) - ->method('query') - ->will($this->returnValue(array($entity))); - $repo = new Repository($solr, $entity); + $solr = new SolrClientFake(); + $solr->mapper = $this->mapper; + $solr->response = array($entity); + + $repo = new Repository($solr, $metaInformation); $actual = $repo->find(2); $this->assertTrue($actual instanceof ValidTestEntity, 'find return no entity'); + + $this->assertTrue($solr->query instanceof FindByIdentifierQuery); + $this->assertEquals('*:*', $solr->query->getQuery()); + $this->assertEquals('id:validtestentity_2', $solr->query->getFilterQuery('id')->getQuery()); } public function testFindAll() { - $document = new Document(); - $document->addField('id', 2); - $document->addField('document_name_s', 'post'); - - $metaFactory = $this->getMock( - 'FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory', - array(), - array(), - '', - false - ); - $metaFactory->expects($this->once()) - ->method('loadInformation') - ->will($this->returnValue(MetaTestInformationFactory::getMetaInformation())); - - $mapper = $this->getMock('FS\SolrBundle\Doctrine\Mapper\EntityMapper', array(), array(), '', false); - $mapper->expects($this->once()) - ->method('toDocument') - ->will($this->returnValue($document)); - - $solr = $this->getMock('FS\SolrBundle\Solr', array(), array(), '', false); - $solr->expects($this->exactly(2)) - ->method('getMapper') - ->will($this->returnValue($mapper)); - - $solr->expects($this->once()) - ->method('getCommandFactory') - ->will($this->returnValue(CommandFactoryStub::getFactoryWithAllMappingCommand())); - - $solr->expects($this->once()) - ->method('getMetaFactory') - ->will($this->returnValue($metaFactory)); + $metaInformation = MetaTestInformationFactory::getMetaInformation(); $entity = new ValidTestEntity(); - $solr->expects($this->once()) - ->method('query') - ->will($this->returnValue(array($entity))); - $repo = new Repository($solr, $entity); + $solr = new SolrClientFake(); + $solr->mapper = $this->mapper; + $solr->response = array($entity); + + $repo = new Repository($solr, $metaInformation); $actual = $repo->findAll(); $this->assertTrue(is_array($actual)); - $this->assertNull($document->id, 'id was removed'); + $this->assertTrue($solr->query instanceof FindByDocumentNameQuery); + $this->assertEquals('*:*', $solr->query->getQuery()); + $this->assertEquals('id:validtestentity_*', $solr->query->getFilterQuery('id')->getQuery()); } public function testFindBy() @@ -115,27 +91,74 @@ public function testFindBy() 'text' => 'bar' ); - $solr = $this->getMock('FS\SolrBundle\Solr', array(), array(), '', false); - $query = $this->getMock('FS\SolrBundle\Query\SolrQuery', array(), array(), '', false); - $query->expects($this->exactly(2)) - ->method('addSearchTerm'); + $metaInformation = MetaTestInformationFactory::getMetaInformation(); - $solr->expects($this->once()) - ->method('createQuery') - ->will($this->returnValue($query)); + $entity = new ValidTestEntity(); - $solr->expects($this->once()) - ->method('query') - ->with($query) - ->will($this->returnValue(array())); + $solr = new SolrClientFake(); + $solr->mapper = $this->mapper; + $solr->response = array($entity); + $solr->metaFactory = $this->metaInformationFactory; - $entity = new ValidTestEntity(); - $repo = new Repository($solr, $entity); + $repo = new Repository($solr, $metaInformation); $found = $repo->findBy($fields); $this->assertTrue(is_array($found)); + + $this->assertTrue($solr->query instanceof AbstractQuery); + $this->assertEquals('title:foo AND text_t:bar', $solr->query->getQuery()); + $this->assertEquals('id:validtestentity_*', $solr->query->getFilterQuery('id')->getQuery()); } + public function testFindOneBy() + { + $fields = array( + 'title' => 'foo', + 'text' => 'bar' + ); + + $metaInformation = MetaTestInformationFactory::getMetaInformation(); + + $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($fields); + + $this->assertEquals($entity, $found); + + $this->assertTrue($solr->query instanceof AbstractQuery); + $this->assertEquals('title:foo AND text_t:bar', $solr->query->getQuery()); + $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()); + } } diff --git a/Tests/Resources/config/schema.xml b/Tests/Resources/config/schema.xml new file mode 100644 index 00000000..0f9a969b --- /dev/null +++ b/Tests/Resources/config/schema.xml @@ -0,0 +1,721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/SolrClientFake.php b/Tests/SolrClientFake.php index e0318009..ed20801a 100644 --- a/Tests/SolrClientFake.php +++ b/Tests/SolrClientFake.php @@ -1,13 +1,45 @@ mapper; + } + + public function getCommandFactory() + { + return $this->commandFactory; + } - public function addDocument($doc) + public function getMetaFactory() { + return $this->metaFactory; + } + + public function addDocument($doc): bool + { + return true; } public function deleteByQuery($query) @@ -24,8 +56,10 @@ public function isCommited() return $this->commit; } - public function query() + public function query(AbstractQuery $query): array { + $this->query = $query; + return $this->response; } @@ -38,4 +72,45 @@ public function getOptions() { return array(); } + + public function createQuery($entity) + { + $metaInformation = $this->metaFactory->loadInformation($entity); + $class = $metaInformation->getClassName(); + $entity = new $class; + + $query = new SolrQuery(); + $query->setSolr($this); + $query->setEntity($entity); + $query->setIndex($metaInformation->getIndex()); + $query->setMetaInformation($metaInformation); + $query->setMappedFields($metaInformation->getFieldMapping()); + + return $query; + } + + public function removeDocument($entity) + { + // TODO: Implement removeDocument() method. + } + + public function updateDocument($entity): bool + { + // TODO: Implement updateDocument() method. + } + + public function getRepository($entity): RepositoryInterface + { + // TODO: Implement getRepository() method. + } + + public function computeChangeSet(array $doctrineChangeSet, $entity) + { + // TODO: Implement computeChangeSet() method. + } + + public function createQueryBuilder($entity): QueryBuilderInterface + { + // TODO: Implement createQueryBuilder() method. + } } diff --git a/Tests/SolrTest.php b/Tests/SolrTest.php index b442952d..cd489043 100644 --- a/Tests/SolrTest.php +++ b/Tests/SolrTest.php @@ -2,155 +2,51 @@ namespace FS\SolrBundle\Tests; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\InvalidTestEntityFiltered; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidTestEntityFiltered; +use FS\SolrBundle\Query\QueryBuilderInterface; +use FS\SolrBundle\Tests\Fixtures\EntityWithInvalidRepository; +use FS\SolrBundle\Tests\Fixtures\InvalidTestEntityFiltered; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntityFiltered; +use FS\SolrBundle\Tests\Fixtures\EntityCore0; +use FS\SolrBundle\Tests\Fixtures\EntityCore1; use FS\SolrBundle\Tests\Doctrine\Mapper\SolrDocumentStub; -use FS\SolrBundle\Tests\ResultFake; -use FS\SolrBundle\Tests\SolrResponseFake; use FS\SolrBundle\Query\FindByDocumentNameQuery; -use FS\SolrBundle\Event\EventManager; -use FS\SolrBundle\Tests\SolrClientFake; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\EntityWithRepository; -use FS\SolrBundle\Doctrine\Mapper\MetaInformation; -use FS\SolrBundle\Tests\Util\MetaTestInformationFactory; -use FS\SolrBundle\Solr; -use FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidEntityRepository; -use FS\SolrBundle\Tests\Util\CommandFactoryStub; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; +use FS\SolrBundle\Tests\Fixtures\EntityWithRepository; +use FS\SolrBundle\Tests\Fixtures\ValidEntityRepository; use FS\SolrBundle\Query\SolrQuery; +use Solarium\Plugin\BufferedAdd\BufferedAdd; use Solarium\QueryType\Update\Query\Document\Document; /** * * @group facade */ -class SolrTest extends \PHPUnit_Framework_TestCase +class SolrTest extends AbstractSolrTest { - protected $metaFactory = null; - protected $config = null; - protected $commandFactory = null; - protected $eventDispatcher = null; - protected $mapper = null; - protected $solrClientFake = null; - - public function setUp() - { - $this->metaFactory = $metaFactory = $this->getMock( - 'FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory', - array(), - array(), - '', - false - ); - $this->config = $this->getMock('FS\SolrBundle\SolrConnection', array(), array(), '', false); - $this->commandFactory = CommandFactoryStub::getFactoryWithAllMappingCommand(); - $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcher', array(), array(), '', false); - $this->mapper = $this->getMock('FS\SolrBundle\Doctrine\Mapper\EntityMapper', array(), array(), '', false); - - $this->solrClientFake = $this->getMock('Solarium\Client', array(), array(), '', false); - } - - private function assertUpdateQueryExecuted() - { - $updateQuery = $this->getMock('Solarium\QueryType\Update\Query\Query', array(), array(), '', false); - $updateQuery->expects($this->once()) - ->method('addDocument'); - - $updateQuery->expects($this->once()) - ->method('addCommit'); - - $this->solrClientFake - ->expects($this->once()) - ->method('createUpdate') - ->will($this->returnValue($updateQuery)); - } - - private function assertUpdateQueryWasNotExecuted() - { - $updateQuery = $this->getMock('Solarium\QueryType\Update\Query\Query', array(), array(), '', false); - $updateQuery->expects($this->never()) - ->method('addDocument'); - - $updateQuery->expects($this->never()) - ->method('addCommit'); - - $this->solrClientFake - ->expects($this->never()) - ->method('createUpdate'); - } - - protected function assertDeleteQueryWasExecuted() - { - $deleteQuery = $this->getMock('Solarium\QueryType\Update\Query\Query', array(), array(), '', false); - $deleteQuery->expects($this->once()) - ->method('addDeleteQuery') - ->with($this->isType('string')); - - $deleteQuery->expects($this->once()) - ->method('addCommit'); - - $this->solrClientFake - ->expects($this->once()) - ->method('createUpdate') - ->will($this->returnValue($deleteQuery)); - - $this->solrClientFake - ->expects($this->once()) - ->method('update') - ->with($deleteQuery); - } - - protected function setupMetaFactoryLoadOneCompleteInformation($metaInformation = null) - { - if (null === $metaInformation) { - $metaInformation = MetaTestInformationFactory::getMetaInformation(); - } - - $this->metaFactory->expects($this->once()) - ->method('loadInformation') - ->will($this->returnValue($metaInformation)); - } - public function testCreateQuery_ValidEntity() { - $this->setupMetaFactoryLoadOneCompleteInformation(); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $query = $solr->createQuery('FSBlogBundle:ValidTestEntity'); + $query = $this->solr->createQuery(ValidTestEntity::class); $this->assertTrue($query instanceof SolrQuery); - $this->assertEquals(4, count($query->getMappedFields())); + $this->assertEquals(6, count($query->getMappedFields())); } public function testGetRepository_UserdefinedRepository() { - $metaInformation = new MetaInformation(); - $metaInformation->setClassName(get_class(new EntityWithRepository())); - $metaInformation->setRepository('FS\SolrBundle\Tests\Doctrine\Annotation\Entities\ValidEntityRepository'); - - $this->setupMetaFactoryLoadOneCompleteInformation($metaInformation); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $actual = $solr->getRepository('Tests:EntityWithRepository'); + $actual = $this->solr->getRepository(EntityWithRepository::class); $this->assertTrue($actual instanceof ValidEntityRepository); } /** - * @expectedException RuntimeException + * @expectedException \FS\SolrBundle\SolrException + * @expectedExceptionMessage FS\SolrBundle\Tests\Fixtures\InvalidEntityRepository must extends the FS\SolrBundle\Repository\Repository */ public function testGetRepository_UserdefinedInvalidRepository() { - $metaInformation = new MetaInformation(); - $metaInformation->setClassName(get_class(new EntityWithRepository())); - $metaInformation->setRepository('FS\SolrBundle\Tests\Doctrine\Annotation\Entities\InvalidEntityRepository'); - - $this->setupMetaFactoryLoadOneCompleteInformation($metaInformation); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->getRepository('Tests:EntityWithInvalidRepository'); + $this->solr->getRepository(EntityWithInvalidRepository::class); } public function testAddDocument() @@ -160,12 +56,14 @@ public function testAddDocument() $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch'); - $this->mapOneDocument(); + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); - $this->setupMetaFactoryLoadOneCompleteInformation(); + $entity = new ValidTestEntity(); + $entity->setTitle('title'); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->addDocument(new ValidTestEntity()); + $this->solr->addDocument($entity); } public function testUpdateDocument() @@ -175,12 +73,27 @@ public function testUpdateDocument() $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch'); - $this->mapOneDocument(); + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); + + $entity = new ValidTestEntity(); + $entity->setTitle('title'); + + $this->solr->updateDocument($entity); + } + + public function testDoNotUpdateDocumentIfDocumentCallbackAvoidIt() + { + $this->eventDispatcher->expects($this->never()) + ->method('dispatch'); + + $this->assertUpdateQueryWasNotExecuted(); - $this->setupMetaFactoryLoadOneCompleteInformation(); + $filteredEntity = new ValidTestEntityFiltered(); + $filteredEntity->shouldIndex = false; - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->updateDocument(new ValidTestEntity()); + $this->solr->updateDocument($filteredEntity); } public function testRemoveDocument() @@ -190,14 +103,11 @@ public function testRemoveDocument() $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch'); - $this->setupMetaFactoryLoadOneCompleteInformation(); - $this->mapper->expects($this->once()) ->method('toDocument') ->will($this->returnValue(new DocumentStub())); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->removeDocument(new ValidTestEntity()); + $this->solr->removeDocument(new ValidTestEntity()); } public function testClearIndex() @@ -211,42 +121,22 @@ public function testClearIndex() $this->assertDeleteQueryWasExecuted(); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->clearIndex(); - } - - private function assertQueryWasExecuted($data = array()) - { - $selectQuery = $this->getMock('Solarium\QueryType\Select\Query\Query', array(), array(), '', false); - $selectQuery->expects($this->once()) - ->method('setQuery'); - - $queryResult = new ResultFake($data); - - $this->solrClientFake - ->expects($this->once()) - ->method('createSelect') - ->will($this->returnValue($selectQuery)); - - $this->solrClientFake - ->expects($this->once()) - ->method('select') - ->with($selectQuery) - ->will($this->returnValue($queryResult)); + $this->solr->clearIndex(); } public function testQuery_NoResponseKeyInResponseSet() { - $this->assertQueryWasExecuted(); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $document = new Document(); $document->addField('document_name_s', 'name'); + $query = new FindByDocumentNameQuery(); + $query->setDocumentName('name'); $query->setDocument($document); + $query->setIndex('index0'); - $entities = $solr->query($query); + $this->assertQueryWasExecuted(array(), 'index0'); + + $entities = $this->solr->query($query); $this->assertEquals(0, count($entities)); } @@ -254,17 +144,18 @@ public function testQuery_OneDocumentFound() { $arrayObj = new SolrDocumentStub(array('title_s' => 'title')); - $this->assertQueryWasExecuted(array($arrayObj)); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $document = new Document(); $document->addField('document_name_s', 'name'); + $query = new FindByDocumentNameQuery(); + $query->setDocumentName('name'); $query->setDocument($document); $query->setEntity(new ValidTestEntity()); + $query->setIndex('index0'); + + $this->assertQueryWasExecuted(array($arrayObj), 'index0'); - $entities = $solr->query($query); + $entities = $this->solr->query($query); $this->assertEquals(1, count($entities)); } @@ -277,38 +168,33 @@ public function testAddEntity_ShouldNotIndexEntity() $entity = new ValidTestEntityFiltered(); - $information = new MetaInformation(); - $information->setSynchronizationCallback('shouldBeIndex'); - $this->setupMetaFactoryLoadOneCompleteInformation($information); - - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->addDocument($entity); + $this->solr->addDocument($entity); $this->assertTrue($entity->getShouldBeIndexedWasCalled(), 'filter method was not called'); } public function testAddEntity_ShouldIndexEntity() { - $this->assertUpdateQueryExecuted(); + $this->assertUpdateQueryExecuted('index0'); - $this->eventDispatcher->expects($this->exactly(2)) + $this->eventDispatcher->expects($this->any()) ->method('dispatch'); $entity = new ValidTestEntityFiltered(); $entity->shouldIndex = true; - $information = MetaTestInformationFactory::getMetaInformation(); - $information->setSynchronizationCallback('shouldBeIndex'); - $this->setupMetaFactoryLoadOneCompleteInformation($information); - - $this->mapOneDocument(); + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - $solr->addDocument($entity); + $this->solr->addDocument($entity); $this->assertTrue($entity->getShouldBeIndexedWasCalled(), 'filter method was not called'); } + /** + * @expectedException \FS\SolrBundle\SolrException + */ public function testAddEntity_FilteredEntityWithUnknownCallback() { $this->assertUpdateQueryWasNotExecuted(); @@ -316,25 +202,84 @@ public function testAddEntity_FilteredEntityWithUnknownCallback() $this->eventDispatcher->expects($this->never()) ->method('dispatch'); - $information = MetaTestInformationFactory::getMetaInformation(); - $information->setSynchronizationCallback('shouldBeIndex'); - $this->setupMetaFactoryLoadOneCompleteInformation($information); + $this->solr->addDocument(new InvalidTestEntityFiltered()); + } + + /** + * @test + */ + public function indexDocumentsGroupedByCore() + { + $entity = new ValidTestEntity(); + $entity->setTitle('title field'); + + $bufferPlugin = $this->createMock(BufferedAdd::class); + + $bufferPlugin->expects($this->once()) + ->method('setEndpoint') + ->with(null); + + $bufferPlugin->expects($this->once()) + ->method('commit'); + + $this->solrClientFake->expects($this->once()) + ->method('getPlugin') + ->with('bufferedadd') + ->will($this->returnValue($bufferPlugin)); + - $solr = new Solr($this->solrClientFake, $this->commandFactory, $this->eventDispatcher, $this->metaFactory, $this->mapper); - try { - $solr->addDocument(new InvalidTestEntityFiltered()); + $this->mapper->expects($this->once()) + ->method('toDocument') + ->will($this->returnValue(new DocumentStub())); - $this->fail('BadMethodCallException expected'); - } catch (\BadMethodCallException $e) { - $this->assertTrue(true); - } + $this->solr->synchronizeIndex(array($entity)); } - protected function mapOneDocument() + /** + * @test + */ + public function setCoreToNullIfNoIndexExists() { - $this->mapper->expects($this->once()) + $entity1 = new EntityCore0(); + $entity1->setText('a text'); + + $entity2 = new EntityCore1(); + $entity2->setText('a text'); + + $bufferPlugin = $this->createMock(BufferedAdd::class); + + $bufferPlugin->expects($this->at(2)) + ->method('setEndpoint') + ->with('core0'); + + $bufferPlugin->expects($this->at(5)) + ->method('setEndpoint') + ->with('core1'); + + $bufferPlugin->expects($this->exactly(2)) + ->method('commit'); + + $this->solrClientFake->expects($this->once()) + ->method('getPlugin') + ->with('bufferedadd') + ->will($this->returnValue($bufferPlugin)); + + + $this->mapper->expects($this->exactly(2)) ->method('toDocument') - ->will($this->returnValue($this->getMock('Solarium\QueryType\Update\Query\Document\DocumentInterface'))); + ->will($this->returnValue(new DocumentStub())); + + $this->solr->synchronizeIndex(array($entity1, $entity2)); + } + + /** + * @test + */ + public function createQueryBuilder() + { + $queryBuilder = $this->solr->createQueryBuilder(ValidTestEntity::class); + + $this->assertTrue($queryBuilder instanceof QueryBuilderInterface); } } diff --git a/Tests/Util/CommandFactoryStub.php b/Tests/Util/CommandFactoryStub.php index d907b7b3..4f148a47 100644 --- a/Tests/Util/CommandFactoryStub.php +++ b/Tests/Util/CommandFactoryStub.php @@ -5,6 +5,7 @@ use FS\SolrBundle\Doctrine\Mapper\Mapping\CommandFactory; use FS\SolrBundle\Doctrine\Mapper\Mapping\MapAllFieldsCommand; use FS\SolrBundle\Doctrine\Mapper\Mapping\MapIdentifierCommand; +use FS\SolrBundle\Doctrine\Mapper\MetaInformationFactory; class CommandFactoryStub { @@ -14,8 +15,10 @@ class CommandFactoryStub */ public static function getFactoryWithAllMappingCommand() { + $reader = new AnnotationReader(new \Doctrine\Common\Annotations\AnnotationReader()); + $commandFactory = new CommandFactory(); - $commandFactory->add(new MapAllFieldsCommand(), 'all'); + $commandFactory->add(new MapAllFieldsCommand(new MetaInformationFactory($reader)), 'all'); $commandFactory->add(new MapIdentifierCommand(), 'identifier'); return $commandFactory; diff --git a/Tests/Util/EntityIdentifier.php b/Tests/Util/EntityIdentifier.php index 286aeef1..203e1873 100644 --- a/Tests/Util/EntityIdentifier.php +++ b/Tests/Util/EntityIdentifier.php @@ -7,6 +7,6 @@ class EntityIdentifier { public static function generate() { - return rand(1, 15); + return rand(1, 100000000); } } \ No newline at end of file diff --git a/Tests/Util/MetaTestInformationFactory.php b/Tests/Util/MetaTestInformationFactory.php index 6d40955e..ad9fcfc8 100644 --- a/Tests/Util/MetaTestInformationFactory.php +++ b/Tests/Util/MetaTestInformationFactory.php @@ -2,22 +2,27 @@ namespace FS\SolrBundle\Tests\Util; use FS\SolrBundle\Doctrine\Annotation\Field; -use FS\SolrBundle\Tests\Doctrine\Mapper\ValidTestEntity; +use FS\SolrBundle\Doctrine\Annotation\Id; +use FS\SolrBundle\Tests\Fixtures\ValidTestEntity; use FS\SolrBundle\Doctrine\Mapper\MetaInformation; class MetaTestInformationFactory { /** + * @param object $entity + * * @return MetaInformation */ - public static function getMetaInformation() + public static function getMetaInformation($entity = null) { - $entity = new ValidTestEntity(); + if ($entity === null) { + $entity = new ValidTestEntity(); + } $entity->setId(2); $metaInformation = new MetaInformation(); - $title = new Field(array('name' => 'title', 'type' => 'string', 'boost' => '1.8', 'value' => 'A title')); + $title = new Field(array('name' => 'title', 'boost' => '1.8', 'value' => 'A title')); $text = new Field(array('name' => 'text', 'type' => 'text', 'value' => 'A text')); $createdAt = new Field(array('name' => 'created_at', 'type' => 'date', 'boost' => '1', 'value' => 'A created at')); @@ -29,6 +34,7 @@ public static function getMetaInformation() 'text_t' => 'text', 'created_at_dt' => 'created_at' ); + $metaInformation->setIdentifier(new Id(array())); $metaInformation->setBoost(1); $metaInformation->setFieldMapping($fieldMapping); $metaInformation->setEntity($entity); diff --git a/behat.phar b/behat.phar deleted file mode 100644 index 48089f28..00000000 Binary files a/behat.phar and /dev/null differ diff --git a/behat.yml b/behat.yml deleted file mode 100644 index c13e6411..00000000 --- a/behat.yml +++ /dev/null @@ -1,4 +0,0 @@ -default: - paths: - features: Tests/Integration/Features - bootstrap: Tests/Integration/Bootstrap \ No newline at end of file diff --git a/composer.json b/composer.json index 496f7d15..16cf4272 100644 --- a/composer.json +++ b/composer.json @@ -1,33 +1,49 @@ { - "name": "floriansemm/solr-bundle", - "type": "symfony-bundle", - "description": "Symfony2 Solr integration bundle", - "keywords": ["search", "index", "symfony", "solr"], - "homepage": "https://github.com/floriansemm/SolrBundle", - "license": "MIT", - "require": { - "php": ">=5.3.2", - "solarium/solarium": "*" - }, - "require-dev": { - "doctrine/doctrine-module": "*", - "doctrine/doctrine-orm-module": "*", - "doctrine/mongodb": "*", - "doctrine/mongodb-odm": "*", - "symfony/dependency-injection": "*", - "symfony/http-kernel": "*", - "symfony/config": "*", - "symfony/doctrine-bridge": "*" - }, - "minimum-stability": "alpha", - "autoload": { - "psr-0": { - "FS\\SolrBundle": "" - } - }, - "suggest": { - "doctrine/mongodb": "Required if you want to use the MongoDB ODM features", - "doctrine/mongodb-odm": "Required if you want to use the MongoDB ODM features" - }, - "target-dir": "FS/SolrBundle" + "name": "floriansemm/solr-bundle", + "type": "symfony-bundle", + "description": "Symfony Solr integration bundle", + "keywords": [ + "search", + "index", + "symfony", + "solr" + ], + "homepage": "https://github.com/floriansemm/SolrBundle", + "license": "MIT", + "require": { + "php": "^7.0", + "solarium/solarium": "^4.0", + "symfony/dependency-injection": "^2.3|^3.0|^4.0", + "symfony/http-kernel": "^2.3|^3.0|^4.0", + "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", + "myclabs/deep-copy": "^1.6", + "doctrine/annotations": "^1.4" + }, + "require-dev": { + "doctrine/mongodb-odm-bundle": "*", + "doctrine/orm": "^2.3", + "phpunit/phpunit": "^5.4" + }, + "minimum-stability": "RC", + "autoload": { + "psr-0": { + "FS\\SolrBundle": "" + } + }, + "config": { + "bin-dir": "bin" + }, + "extras": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "suggest": { + "doctrine/orm": "Required if you want to use the Doctrine ORM", + "doctrine/mongodb-odm-bundle": "Required if you want to use the MongoDB ODM" + }, + "target-dir": "FS/SolrBundle" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c7fb3206..b6a426e3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,7 +10,7 @@ stopOnFailure="false" syntaxCheck="false" bootstrap="Tests/bootstrap.php" -> + >