From eba893495d84ac21d46decacd3dd97cf66b74cf6 Mon Sep 17 00:00:00 2001 From: ornicar Date: Wed, 20 Apr 2011 13:53:59 -0700 Subject: [PATCH] Introduce builtin doctrine provider and finder --- DependencyInjection/Configuration.php | 24 ++ DependencyInjection/FOQElasticaExtension.php | 307 +++++++++++------- Finder/FinderInterface.php | 15 + Finder/MappedFinder.php | 63 ++++ Finder/PaginatedFinderInterface.php | 16 + Mapper/DoctrineMapper.php | 32 +- Populator.php | 6 + Provider/DoctrineProvider.php | 22 +- Resources/config/config.xml | 11 + Resources/config/mongodb.xml | 25 ++ .../ObjectToArrayAutomaticTransformer.php | 4 +- 11 files changed, 375 insertions(+), 150 deletions(-) create mode 100644 Finder/FinderInterface.php create mode 100644 Finder/MappedFinder.php create mode 100644 Finder/PaginatedFinderInterface.php create mode 100644 Resources/config/mongodb.xml diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 9bab727..1a5daac 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -86,6 +86,30 @@ class Configuration ->useAttributeAsKey('name') ->prototype('array') ->treatNullLike(array()) + ->children() + ->arrayNode('doctrine') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('driver')->end() + ->scalarNode('model')->end() + ->scalarNode('identifier')->defaultValue('id')->end() + ->arrayNode('provider') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() + ->scalarNode('batch_size')->defaultValue(100)->end() + ->scalarNode('clear_object_manager')->defaultTrue()->end() + ->end() + ->end() + ->arrayNode('finder') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('hydrate')->defaultTrue()->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->append($this->getMappingsNode()) ->end() ; diff --git a/DependencyInjection/FOQElasticaExtension.php b/DependencyInjection/FOQElasticaExtension.php index ad37fc2..ae66cf0 100644 --- a/DependencyInjection/FOQElasticaExtension.php +++ b/DependencyInjection/FOQElasticaExtension.php @@ -7,151 +7,208 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Config\FileLocator; use InvalidArgumentException; class FOQElasticaExtension extends Extension { - protected $typeMappings = array(); + protected $supportedProviderDrivers = array('mongodb'); + protected $typeMappings = array(); + protected $loadedDoctrineDrivers = array(); - public function load(array $configs, ContainerBuilder $container) - { - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('config.xml'); + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $processor = new Processor(); + $config = $processor->process($configuration->getConfigTree(), $configs); - $configuration = new Configuration(); - $processor = new Processor(); + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('config.xml'); - $config = $processor->process($configuration->getConfigTree(), $configs); + if (empty($config['clients']) || empty($config['indexes'])) { + throw new InvalidArgumentException('You must define at least one client and one index'); + } - if (empty($config['clients']) || empty($config['indexes'])) { - throw new InvalidArgumentException('You must define at least one client and one index'); - } + if (empty($config['default_client'])) { + $keys = array_keys($config['clients']); + $config['default_client'] = reset($keys); + } - if (empty($config['default_client'])) { - $keys = array_keys($config['clients']); - $config['default_client'] = reset($keys); - } + if (empty($config['default_index'])) { + $keys = array_keys($config['indexes']); + $config['default_index'] = reset($keys); + } - if (empty($config['default_index'])) { - $keys = array_keys($config['indexes']); - $config['default_index'] = reset($keys); - } + $clientIdsByName = $this->loadClients($config['clients'], $container); + $indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']); + $indexDefsByName = array_map(function($id) use ($container) { + return $container->getDefinition($id); + }, $indexIdsByName); - $clientIdsByName = $this->loadClients($config['clients'], $container); - $indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']); - $indexDefsByName = array_map(function($id) use ($container) { - return $container->getDefinition($id); - }, $indexIdsByName); + $this->loadIndexManager($indexDefsByName, $container->getDefinition($indexIdsByName[$config['default_index']]), $container); + $this->loadMappingSetter($this->typeMappings, $container); - $this->loadIndexManager($indexDefsByName, $container->getDefinition($indexIdsByName[$config['default_index']]), $container); - $this->loadMappingSetter($this->typeMappings, $container); + $container->setAlias('foq_elastica.client', sprintf('foq_elastica.client.%s', $config['default_client'])); + $container->setAlias('foq_elastica.index', sprintf('foq_elastica.index.%s', $config['default_index'])); + } - $container->setAlias('foq_elastica.client', sprintf('foq_elastica.client.%s', $config['default_client'])); - $container->setAlias('foq_elastica.index', sprintf('foq_elastica.index.%s', $config['default_index'])); - } + /** + * Loads the configured clients. + * + * @param array $config An array of clients configurations + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadClients(array $clients, ContainerBuilder $container) + { + $clientIds = array(); + foreach ($clients as $name => $client) { + $clientDefArgs = array( + isset($client['host']) ? $client['host'] : null, + isset($client['port']) ? $client['port'] : array(), + ); + $clientDef = new Definition('%foq_elastica.client.class%', $clientDefArgs); + $clientId = sprintf('foq_elastica.client.%s', $name); + $container->setDefinition($clientId, $clientDef); + $clientIds[$name] = $clientId; + } - /** - * Loads the configured clients. - * - * @param array $config An array of clients configurations - * @param ContainerBuilder $container A ContainerBuilder instance - */ - protected function loadClients(array $clients, ContainerBuilder $container) - { - $clientIds = array(); - foreach ($clients as $name => $client) { - $clientDefArgs = array( - isset($client['host']) ? $client['host'] : null, - isset($client['port']) ? $client['port'] : array(), - ); - $clientDef = new Definition('%foq_elastica.client.class%', $clientDefArgs); - $clientId = sprintf('foq_elastica.client.%s', $name); - $container->setDefinition($clientId, $clientDef); - $clientIds[$name] = $clientId; - } + return $clientIds; + } - return $clientIds; - } + /** + * Loads the configured indexes. + * + * @param array $config An array of indexes configurations + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadIndexes(array $indexes, ContainerBuilder $container, array $clientIdsByName, $defaultClientName) + { + $indexIds = array(); + foreach ($indexes as $name => $index) { + if (isset($index['client'])) { + $clientName = $index['client']; + if (!isset($clientIdsByName[$clientName])) { + throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName)); + } + } else { + $clientName = $defaultClientName; + } + $clientId = $clientIdsByName[$clientName]; + $clientDef = $container->getDefinition($clientId); + $indexId = sprintf('foq_elastica.index.%s', $name); + $indexDefArgs = array($name); + $indexDef = new Definition('%foq_elastica.index.class%', $indexDefArgs); + $indexDef->setFactoryService($clientId); + $indexDef->setFactoryMethod('getIndex'); + $container->setDefinition($indexId, $indexDef); + $this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId); + $indexIds[$name] = $indexId; + } - /** - * Loads the configured indexes. - * - * @param array $config An array of indexes configurations - * @param ContainerBuilder $container A ContainerBuilder instance - */ - protected function loadIndexes(array $indexes, ContainerBuilder $container, array $clientIdsByName, $defaultClientName) - { - $indexIds = array(); - foreach ($indexes as $name => $index) { - if (isset($index['client'])) { - $clientName = $index['client']; - if (!isset($clientIdsByName[$clientName])) { - throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName)); - } - } else { - $clientName = $defaultClientName; - } - $clientId = $clientIdsByName[$clientName]; - $clientDef = $container->getDefinition($clientId); - $indexId = sprintf('foq_elastica.index.%s', $name); - $indexDefArgs = array($name); - $indexDef = new Definition('%foq_elastica.index.class%', $indexDefArgs); - $indexDef->setFactoryService($clientId); - $indexDef->setFactoryMethod('getIndex'); - $container->setDefinition($indexId, $indexDef); - $this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $indexId); - $indexIds[$name] = $indexId; - } + return $indexIds; + } - return $indexIds; - } + /** + * Loads the configured types. + * + * @param array $config An array of types configurations + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function loadTypes(array $types, ContainerBuilder $container, $indexName, $indexId) + { + foreach ($types as $name => $type) { + $typeId = sprintf('%s.%s', $indexId, $name); + $typeDefArgs = array($name); + $typeDef = new Definition('%foq_elastica.type.class%', $typeDefArgs); + $typeDef->setFactoryService($indexId); + $typeDef->setFactoryMethod('getType'); + $container->setDefinition($typeId, $typeDef); + if (isset($type['mappings'])) { + $this->typeMappings[] = array( + new Reference($typeId), + $type['mappings'] + ); + } + if (isset($type['doctrine'])) { + $this->loadTypeDoctrineIntegration($type['doctrine'], $container, $typeDef, $indexName, $name); + } + } + } - /** - * Loads the configured types. - * - * @param array $config An array of types configurations - * @param ContainerBuilder $container A ContainerBuilder instance - */ - protected function loadTypes(array $types, ContainerBuilder $container, $indexId) - { - foreach ($types as $name => $type) { - $typeId = sprintf('%s.%s', $indexId, $name); - $typeDefArgs = array($name); - $typeDef = new Definition('%foq_elastica.type.class%', $typeDefArgs); - $typeDef->setFactoryService($indexId); - $typeDef->setFactoryMethod('getType'); - $container->setDefinition($typeId, $typeDef); - if (isset($type['mappings'])) { - $this->typeMappings[] = array( - new Reference($typeId), - $type['mappings'] - ); - } - } - } + /** + * Loads the optional provider and finder for a type + * + * @return null + **/ + public function loadTypeDoctrineIntegration(array $config, ContainerBuilder $container, Definition $typeDef, $indexName, $typeName) + { + if (!in_array($config['driver'], $this->supportedProviderDrivers)) { + throw new InvalidArgumentException(sprintf('The provider driver "%s" is not supported')); + } + $this->loadDoctrineDriver($container, $config['driver']); + if (isset($config['provider'])) { + $abstractProviderId = sprintf('foq_elastica.provider.prototype.%s', $config['driver']); + $providerId = sprintf('foq_elastica.provider.%s.%s', $indexName, $typeName); + $providerDef = new DefinitionDecorator($abstractProviderId); + $providerDef->setArgument(0, $typeDef); + $providerDef->setArgument(3, $config['model']); + $providerDef->setArgument(4, array_merge($config['provider'], array( + 'identifier' => $config['identifier'] + ))); + $container->setDefinition($providerId, $providerDef); + $container->getDefinition('foq_elastica.populator') + ->addMethodCall('addProvider', array($providerId, new Reference($providerId))); + } + if (isset($config['finder'])) { + $abstractMapperId = sprintf('foq_elastica.mapper.prototype.%s', $config['driver']); + $mapperId = sprintf('foq_elastica.mapper.%s.%s', $indexName, $typeName); + $mapperDef = new DefinitionDecorator($abstractMapperId); + $mapperDef->setArgument(1, $config['model']); + $mapperDef->setArgument(2, array_merge($config['finder'], array( + 'identifier' => $config['identifier'] + ))); + $container->setDefinition($mapperId, $mapperDef); + $abstractFinderId = 'foq_elastica.finder.prototype'; + $finderId = sprintf('foq_elastica.finder.%s.%s', $indexName, $typeName); + $finderDef = new DefinitionDecorator($abstractFinderId); + $finderDef->setArgument(0, $typeDef); + $finderDef->setArgument(1, new Reference($mapperId)); + $container->setDefinition($finderId, $finderDef); + } + } - /** - * Loads the index manager - * - * @return null - **/ - public function loadIndexManager(array $indexDefs, $defaultIndexId, ContainerBuilder $container) - { - $managerDef = $container->getDefinition('foq_elastica.index_manager'); - $managerDef->setArgument(0, $indexDefs); - $managerDef->setArgument(1, new Reference('foq_elastica.index')); - } + /** + * Loads the index manager + * + * @return null + **/ + public function loadIndexManager(array $indexDefs, $defaultIndexId, ContainerBuilder $container) + { + $managerDef = $container->getDefinition('foq_elastica.index_manager'); + $managerDef->setArgument(0, $indexDefs); + $managerDef->setArgument(1, new Reference('foq_elastica.index')); + } - /** - * Loads the mapping setter - * - * @return null - **/ - public function loadMappingSetter(array $mappings, ContainerBuilder $container) - { - $managerDef = $container->getDefinition('foq_elastica.mapping_setter'); - $managerDef->setArgument(0, $mappings); - } + /** + * Loads the mapping setter + * + * @return null + **/ + public function loadMappingSetter(array $mappings, ContainerBuilder $container) + { + $managerDef = $container->getDefinition('foq_elastica.mapping_setter'); + $managerDef->setArgument(0, $mappings); + } + + protected function loadDoctrineDriver(ContainerBuilder $container, $driver) + { + if (in_array($driver, $this->loadedDoctrineDrivers)) { + return; + } + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load($driver.'.xml'); + $this->loadedDoctrineDrivers[] = $driver; + } } diff --git a/Finder/FinderInterface.php b/Finder/FinderInterface.php new file mode 100644 index 0000000..01e8971 --- /dev/null +++ b/Finder/FinderInterface.php @@ -0,0 +1,15 @@ +searchable = $searchable; + $this->mapper = $mapper; + } + + /** + * Search for a query string in the food searchable + * + * @return array of Food documents + **/ + public function find($query, $limit) + { + $queryObject = Elastica_Query::create($query); + $queryObject->setLimit($limit); + $results = $this->searchable->search($queryObject)->getResults(); + + return $this->mapper->fromElasticaObjects($results); + } + + /** + * Gets a paginator wrapping the result of a search + * + * @return Paginator + **/ + public function findPaginated($query) + { + $queryObject = Elastica_Query::create($query); + $paginatorAdapter = $this->createPaginatorAdapter($queryObject); + $paginator = new Paginator($paginatorAdapter); + + return $this->createPaginator($queryObject); + } + + /** + * Creates a paginator adapter for this query + * + * @param Elastica_Query $query + * @return DoctrinePaginatorAdapter + */ + protected function createPaginatorAdapter(Elastica_Query $query) + { + return new DoctrinePaginatorAdapter($this->searchable, $query, $this->mapper); + } +} diff --git a/Finder/PaginatedFinderInterface.php b/Finder/PaginatedFinderInterface.php new file mode 100644 index 0000000..4c66c3b --- /dev/null +++ b/Finder/PaginatedFinderInterface.php @@ -0,0 +1,16 @@ + true + 'hydrate' => true, + 'identifier' => 'id' ); /** * Instanciates a new Mapper * - * @param ObjectRepository objectRepository - * @param string $identifier + * @param ObjectManager objectManager + * @param string $objectClass * @param array $options */ - public function __construct(ObjectRepository $objectRepository, $identifier = 'id', array $options = array()) + public function __construct(ObjectManager $objectManager, $objectClass, array $options = array()) { - $this->objectRepository = $objectRepository; - $this->identifier = $identifier; - $this->options = array_merge($this->options, $options); + $this->objectManager = $objectManager; + $this->objectClass = $objectClass; + $this->options = array_merge($this->options, $options); } /** @@ -61,13 +62,12 @@ class DoctrineMapper implements MapperInterface return $elasticaObject->getId(); }, $elasticaObjects); - return $this->objectRepository - ->createQueryBuilder() - ->field($this->identifier)->in($ids) + return $this->objectManager + ->createQueryBuilder($this->objectClass) + ->field($this->options['identifier'])->in($ids) ->hydrate($this->options['hydrate']) ->getQuery() ->execute() ->toArray(); } - } diff --git a/Populator.php b/Populator.php index 02f87ff..39287b6 100644 --- a/Populator.php +++ b/Populator.php @@ -2,6 +2,7 @@ namespace FOQ\ElasticaBundle; +use FOQ\ElasticaBundle\Provider\ProviderInterface; use Closure; class Populator @@ -13,6 +14,11 @@ class Populator $this->providers = $providers; } + public function addProvider($name, ProviderInterface $provider) + { + $this->providers[$name] = $provider; + } + public function populate(Closure $loggerClosure) { foreach ($this->providers as $name => $provider) { diff --git a/Provider/DoctrineProvider.php b/Provider/DoctrineProvider.php index cba99eb..64f2a90 100644 --- a/Provider/DoctrineProvider.php +++ b/Provider/DoctrineProvider.php @@ -16,9 +16,10 @@ class DoctrineProvider implements ProviderInterface protected $objectClass; protected $transformer; protected $options = array( - 'batch_size' => 100, - 'clear_object_manager' => true, - 'create_query_builder_method' => 'createQueryBuilder' + 'batch_size' => 100, + 'clear_object_manager' => true, + 'query_builder_method' => 'createQueryBuilder', + 'identifier' => 'id' ); public function __construct(Elastica_Type $type, ObjectManager $objectManager, ObjectToArrayTransformerInterface $transformer, $objectClass, array $options = array()) @@ -40,16 +41,18 @@ class DoctrineProvider implements ProviderInterface $queryBuilder = $this->createQueryBuilder(); $nbObjects = $queryBuilder->getQuery()->count(); $fields = $this->extractTypeFields(); + $identifierGetter = 'get'.ucfirst($this->options['identifier']); for ($offset = 0; $offset < $nbObjects; $offset += $this->options['batch_size']) { - $loggerClosure(sprintf('%0.1f%% (%d/%d)', 100*$offset/$nbObjects, $offset, $nbObjects)); - - $objects = $queryBuilder->limit($this->options['batch_size'])->skip($offset)->getQuery()->execute()->toArray(); $documents = array(); + $objects = $queryBuilder->limit($this->options['batch_size'])->skip($offset)->getQuery()->execute()->toArray(); + $stepCount = (count($objects)+$offset); + $loggerClosure(sprintf('%0.1f%% (%d/%d)', 100*$stepCount/$nbObjects, $stepCount, $nbObjects)); + foreach ($objects as $object) { $data = $this->transformer->transform($object, $fields); - $documents[] = new Elastica_Document($object->getId(), $data); + $documents[] = new Elastica_Document($object->$identifierGetter(), $data); } $this->type->addDocuments($documents); @@ -66,7 +69,7 @@ class DoctrineProvider implements ProviderInterface **/ protected function createQueryBuilder() { - return $this->objectManager->getRepository($this->objectClass)->{$this->options['create_query_builder_method']}(); + return $this->objectManager->getRepository($this->objectClass)->{$this->options['query_builder_method']}(); } protected function extractTypeFields() @@ -77,6 +80,9 @@ class DoctrineProvider implements ProviderInterface // skip type name $mappings = reset($mappings); $mappings = $mappings['properties']; + if (array_key_exists('__isInitialized__', $mappings)) { + unset($mappings['__isInitialized__']); + } return array_keys($mappings); } diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 1742ff3..2a58ba3 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -12,20 +12,31 @@ + + + + + + + + + + + diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml new file mode 100644 index 0000000..8b8aadb --- /dev/null +++ b/Resources/config/mongodb.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Transformer/ObjectToArrayAutomaticTransformer.php b/Transformer/ObjectToArrayAutomaticTransformer.php index d4cab46..9ccd1e2 100644 --- a/Transformer/ObjectToArrayAutomaticTransformer.php +++ b/Transformer/ObjectToArrayAutomaticTransformer.php @@ -2,6 +2,8 @@ namespace FOQ\ElasticaBundle\Transformer; +use RuntimeException; + /** * AutomaticObjectToArrayTransformer * Tries to convert objects by generating getters @@ -23,7 +25,7 @@ class ObjectToArrayAutomaticTransformer implements ObjectToArrayTransformerInter foreach ($requiredKeys as $key) { $getter = 'get'.ucfirst($key); if (!method_exists($class, $getter)) { - throw new InvalidArgumentException(sprintf('The getter %s::%s does not exist', $this->objectClass, $getter)); + throw new RuntimeException(sprintf('The getter %s::%s does not exist', $class, $getter)); } $array[$key] = $object->$getter(); }