diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 8a067b5..fe7e9a4 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -179,6 +179,7 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('hydrate')->defaultTrue()->end() ->scalarNode('ignore_missing')->defaultFalse()->end() + ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() ->scalarNode('service')->end() ->end() ->end() @@ -270,6 +271,7 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('hydrate')->defaultTrue()->end() ->scalarNode('ignore_missing')->defaultFalse()->end() + ->scalarNode('query_builder_method')->defaultValue('createQueryBuilder')->end() ->scalarNode('service')->end() ->end() ->end() @@ -284,6 +286,7 @@ class Configuration implements ConfigurationInterface ->end() ->append($this->getIdNode()) ->append($this->getMappingsNode()) + ->append($this->getDynamicTemplateNode()) ->append($this->getSourceNode()) ->append($this->getBoostNode()) ->append($this->getRoutingNode()) @@ -316,6 +319,37 @@ class Configuration implements ConfigurationInterface return $node; } + /** + * Returns the array node used for "dynamic_templates". + */ + public function getDynamicTemplateNode() + { + $builder = new TreeBuilder(); + $node = $builder->root('dynamic_templates'); + + $node + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ->scalarNode('match')->isRequired()->end() + ->scalarNode('match_mapping_type')->end() + ->arrayNode('mapping') + ->isRequired() + ->children() + ->scalarNode('type')->end() + ->scalarNode('index')->end() + ->arrayNode('fields') + ->children() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $node; + } + /** * @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the field config to * @param array $nestings the nested mappings for the current field level diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php index eb6dd4d..9bf732e 100644 --- a/DependencyInjection/FOSElasticaExtension.php +++ b/DependencyInjection/FOSElasticaExtension.php @@ -232,6 +232,12 @@ class FOSElasticaExtension extends Extension if (isset($type['index'])) { $this->indexConfigs[$indexName]['config']['mappings'][$name]['index'] = $type['index']; } + if (!empty($type['dynamic_templates'])) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'] = array(); + foreach ($type['dynamic_templates'] as $templateName => $templateData) { + $this->indexConfigs[$indexName]['config']['mappings'][$name]['dynamic_templates'][] = array($templateName => $templateData); + } + } } } @@ -304,7 +310,8 @@ class FOSElasticaExtension extends Extension $serviceDef->replaceArgument($argPos + 1, array( 'hydrate' => $typeConfig['elastica_to_model_transformer']['hydrate'], 'identifier' => $typeConfig['identifier'], - 'ignore_missing' => $typeConfig['elastica_to_model_transformer']['ignore_missing'] + 'ignore_missing' => $typeConfig['elastica_to_model_transformer']['ignore_missing'], + 'query_builder_method' => $typeConfig['elastica_to_model_transformer']['query_builder_method'] )); $container->setDefinition($serviceId, $serviceDef); diff --git a/Doctrine/AbstractElasticaToModelTransformer.php b/Doctrine/AbstractElasticaToModelTransformer.php index e8f9472..147067d 100755 --- a/Doctrine/AbstractElasticaToModelTransformer.php +++ b/Doctrine/AbstractElasticaToModelTransformer.php @@ -35,6 +35,7 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran 'hydrate' => true, 'identifier' => 'id', 'ignore_missing' => false, + 'query_builder_method' => 'createQueryBuilder', ); /** diff --git a/Doctrine/MongoDB/ElasticaToModelTransformer.php b/Doctrine/MongoDB/ElasticaToModelTransformer.php index 4c35a0c..855a093 100644 --- a/Doctrine/MongoDB/ElasticaToModelTransformer.php +++ b/Doctrine/MongoDB/ElasticaToModelTransformer.php @@ -22,7 +22,7 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer { return $this->registry ->getManagerForClass($this->objectClass) - ->createQueryBuilder($this->objectClass) + ->{$this->options['query_builder_method']}($this->objectClass) ->field($this->options['identifier'])->in($identifierValues) ->hydrate($hydrate) ->getQuery() diff --git a/Doctrine/ORM/ElasticaToModelTransformer.php b/Doctrine/ORM/ElasticaToModelTransformer.php index 0a889a3..20ec6e8 100644 --- a/Doctrine/ORM/ElasticaToModelTransformer.php +++ b/Doctrine/ORM/ElasticaToModelTransformer.php @@ -12,6 +12,8 @@ use Doctrine\ORM\Query; */ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer { + const ENTITY_ALIAS = 'o'; + /** * Fetch objects for theses identifier values * @@ -25,14 +27,25 @@ class ElasticaToModelTransformer extends AbstractElasticaToModelTransformer return array(); } $hydrationMode = $hydrate ? Query::HYDRATE_OBJECT : Query::HYDRATE_ARRAY; - $qb = $this->registry - ->getManagerForClass($this->objectClass) - ->getRepository($this->objectClass) - ->createQueryBuilder('o'); - /* @var $qb \Doctrine\ORM\QueryBuilder */ - $qb->where($qb->expr()->in('o.'.$this->options['identifier'], ':values')) + + $qb = $this->getEntityQueryBuilder(); + $qb->where($qb->expr()->in(static::ENTITY_ALIAS.'.'.$this->options['identifier'], ':values')) ->setParameter('values', $identifierValues); return $qb->getQuery()->setHydrationMode($hydrationMode)->execute(); } + + /** + * Retrieves a query builder to be used for querying by identifiers + * + * @return \Doctrine\ORM\QueryBuilder + */ + protected function getEntityQueryBuilder() + { + $repository = $this->registry + ->getManagerForClass($this->objectClass) + ->getRepository($this->objectClass); + + return $repository->{$this->options['query_builder_method']}(static::ENTITY_ALIAS); + } } diff --git a/README.md b/README.md index 13d2024..9792139 100644 --- a/README.md +++ b/README.md @@ -822,3 +822,33 @@ fos_elastica: lastlogin: { type: date, format: basic_date_time } birthday: { type: date, format: "yyyy-MM-dd" } ``` + +#### Dynamic templates + +Dynamic templates allow to define mapping templates that will be +applied when dynamic introduction of fields / objects happens. + +[Documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-root-object-type.html#_dynamic_templates) + +```yaml +fos_elastica: + clients: + default: { host: localhost, port: 9200 } + indexes: + site: + types: + user: + dynamic_templates: + my_template_1: + match: apples_* + mapping: + type: float + my_template_2: + match: * + match_mapping_type: string + mapping: + type: string + index: not_analyzed + mappings: + username: { type: string } +``` diff --git a/Resetter.php b/Resetter.php index 9a9da0b..26a6bb5 100644 --- a/Resetter.php +++ b/Resetter.php @@ -78,6 +78,10 @@ class Resetter $mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type'])); } + if (isset($indexConfig['dynamic_templates'])) { + $mapping->setParam('dynamic_templates', $indexConfig['dynamic_templates']); + } + return $mapping; } diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index f410621..ead9977 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -31,4 +31,45 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mappings['format']); $this->assertNull($mappings['format']->getDefaultValue()); } + + public function testDynamicTemplateNodes() + { + $tree = $this->configuration->getConfigTree(); + $children = $tree->getChildren(); + $children = $children['indexes']->getPrototype()->getChildren(); + $typeNodes = $children['types']->getPrototype()->getChildren(); + $dynamicTemplates = $typeNodes['dynamic_templates']->getPrototype()->getChildren(); + + $this->assertArrayHasKey('match', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['match']); + $this->assertNull($dynamicTemplates['match']->getDefaultValue()); + + $this->assertArrayHasKey('match_mapping_type', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $dynamicTemplates['match_mapping_type']); + $this->assertNull($dynamicTemplates['match_mapping_type']->getDefaultValue()); + + $this->assertArrayHasKey('mapping', $dynamicTemplates); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ArrayNode', $dynamicTemplates['mapping']); + } + + public function testDynamicTemplateMappingNodes() + { + $tree = $this->configuration->getConfigTree(); + $children = $tree->getChildren(); + $children = $children['indexes']->getPrototype()->getChildren(); + $typeNodes = $children['types']->getPrototype()->getChildren(); + $dynamicTemplates = $typeNodes['dynamic_templates']->getPrototype()->getChildren(); + $mapping = $dynamicTemplates['mapping']->getChildren(); + + $this->assertArrayHasKey('type', $mapping); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mapping['type']); + $this->assertNull($mapping['type']->getDefaultValue()); + + $this->assertArrayHasKey('index', $mapping); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ScalarNode', $mapping['index']); + $this->assertNull($mapping['index']->getDefaultValue()); + + $this->assertArrayHasKey('fields', $mapping); + $this->assertInstanceOf('Symfony\Component\Config\Definition\ArrayNode', $mapping['fields']); + } } diff --git a/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php new file mode 100644 index 0000000..14f3ffb --- /dev/null +++ b/Tests/Doctrine/ORM/ElasticaToModelTransformerTest.php @@ -0,0 +1,120 @@ +getMockBuilder('Doctrine\ORM\QueryBuilder') + ->disableOriginalConstructor() + ->getMock(); + + $this->repository->expects($this->once()) + ->method('customQueryBuilderCreator') + ->with($this->equalTo(ElasticaToModelTransformer::ENTITY_ALIAS)) + ->will($this->returnValue($qb)); + $this->repository->expects($this->never()) + ->method('createQueryBuilder'); + + $transformer = new ElasticaToModelTransformer($this->registry, $this->objectClass, array( + 'query_builder_method' => 'customQueryBuilderCreator', + )); + + $class = new \ReflectionClass('FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer'); + $method = $class->getMethod('getEntityQueryBuilder'); + $method->setAccessible(true); + + $method->invokeArgs($transformer, array()); + } + + /** + * Tests that the Transformer uses the query_builder_method configuration option + * allowing configuration of createQueryBuilder call. + */ + public function testTransformUsesDefaultQueryBuilderMethodConfiguration() + { + $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') + ->disableOriginalConstructor() + ->getMock(); + + $this->repository->expects($this->never()) + ->method('customQueryBuilderCreator'); + $this->repository->expects($this->once()) + ->method('createQueryBuilder') + ->with($this->equalTo(ElasticaToModelTransformer::ENTITY_ALIAS)) + ->will($this->returnValue($qb)); + + $transformer = new ElasticaToModelTransformer($this->registry, $this->objectClass); + + $class = new \ReflectionClass('FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer'); + $method = $class->getMethod('getEntityQueryBuilder'); + $method->setAccessible(true); + + $method->invokeArgs($transformer, array()); + } + + protected function setUp() + { + if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) { + $this->markTestSkipped('Doctrine Common is not present'); + } + if (!class_exists('Doctrine\ORM\EntityManager')) { + $this->markTestSkipped('Doctrine Common is not present'); + } + + $this->registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry') + ->disableOriginalConstructor() + ->getMock(); + + $this->manager = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->registry->expects($this->any()) + ->method('getManagerForClass') + ->with($this->objectClass) + ->will($this->returnValue($this->manager)); + + $this->repository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository', array( + 'customQueryBuilderCreator', + 'createQueryBuilder', + 'find', + 'findAll', + 'findBy', + 'findOneBy', + 'getClassName' + )); + + $this->manager->expects($this->any()) + ->method('getRepository') + ->with($this->objectClass) + ->will($this->returnValue($this->repository)); + } +} diff --git a/Tests/ResetterTest.php b/Tests/ResetterTest.php index 18cbe06..aa0fbcc 100644 --- a/Tests/ResetterTest.php +++ b/Tests/ResetterTest.php @@ -16,7 +16,10 @@ class ResetterTest extends \PHPUnit_Framework_TestCase 'index' => $this->getMockElasticaIndex(), 'config' => array( 'mappings' => array( - 'a' => array('properties' => array()), + 'a' => array( + 'dynamic_templates' => array(), + 'properties' => array(), + ), 'b' => array('properties' => array()), ), ), @@ -100,6 +103,7 @@ class ResetterTest extends \PHPUnit_Framework_TestCase ->method('delete'); $mapping = Mapping::create($this->indexConfigsByName['foo']['config']['mappings']['a']['properties']); + $mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['mappings']['a']['dynamic_templates']); $type->expects($this->once()) ->method('setMapping') ->with($mapping);