Merge branch 'typed-config'

Conflicts:
	composer.json
This commit is contained in:
Tim Nagel 2014-06-24 09:09:29 +10:00
commit 2a20bb623c
46 changed files with 1758 additions and 1085 deletions

16
Annotation/Search.php Normal file
View file

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

View file

@ -12,15 +12,29 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.0...v3.0.1
To generate a changelog summary since the last version, run To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x` `git log --no-merges --oneline v3.0.0...3.0.x`
* 3.0.0-ALPHA6 (xxxx-xx-xx) * 3.0.0-ALPHA6
* Moved `is_indexable_callback` from the listener properties to a type property called * Moved `is_indexable_callback` from the listener properties to a type property called
`indexable_callback` which is run when both populating and listening for object `indexable_callback` which is run when both populating and listening for object
changes. changes.
* BC BREAK `ObjectPersisterInterface` added method getType() (To be removed during
ObjectPersister refactoring before 3.0.0 stable when ObjectPersister will change)
* AbstractProvider constructor change: Second argument is now an `IndexableInterface` * AbstractProvider constructor change: Second argument is now an `IndexableInterface`
instance. instance.
* Annotation @Search moved to FOS\ElasticaBundle\Annotation\Search with FOS\ElasticaBundle\Configuration\Search deprecated
* Deprecated FOS\ElasticaBundle\Client in favour of FOS\ElasticaBundle\Elastica\Client
* Deprecated FOS\ElasticaBundle\DynamicIndex in favour of FOS\ElasticaBundle\Elastica\Index
* Deprecated FOS\ElasticaBundle\IndexManager in favour of FOS\ElasticaBundle\Index\IndexManager
* Deprecated FOS\ElasticaBundle\Resetter in favour of FOS\ElasticaBundle\Index\Resetter
* 3.0.0-ALPHA5 (2014-05-23)
* Doctrine Provider speed up by disabling persistence logging while populating documents
* 3.0.0-ALPHA4 (2014-04-10)
* Indexes are now capable of logging errors with Elastica
* Fixed deferred indexing of deleted documents
* Resetting an index will now create it even if it doesn't exist
* Bulk upserting of documents is now supported when populating
* 3.0.0-ALPHA3 (2014-04-01) * 3.0.0-ALPHA3 (2014-04-01)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,12 +15,15 @@ class Configuration implements ConfigurationInterface
*/ */
private $supportedDrivers = array('orm', 'mongodb', 'propel'); private $supportedDrivers = array('orm', 'mongodb', 'propel');
private $configArray = array(); /**
* If the kernel is running in debug mode.
*
* @var bool
*/
private $debug; private $debug;
public function __construct($configArray, $debug) public function __construct($debug)
{ {
$this->configArray = $configArray;
$this->debug = $debug; $this->debug = $debug;
} }
@ -104,7 +107,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('port')->end() ->scalarNode('port')->end()
->scalarNode('proxy')->end() ->scalarNode('proxy')->end()
->scalarNode('logger') ->scalarNode('logger')
->defaultValue(($this->debug) ? 'fos_elastica.logger' : false) ->defaultValue($this->debug ? 'fos_elastica.logger' : false)
->treatNullLike('fos_elastica.logger') ->treatNullLike('fos_elastica.logger')
->treatTrueLike('fos_elastica.logger') ->treatTrueLike('fos_elastica.logger')
->end() ->end()
@ -180,11 +183,11 @@ class Configuration implements ConfigurationInterface
->beforeNormalization() ->beforeNormalization()
->ifTrue(function($v) { return isset($v['mappings']); }) ->ifTrue(function($v) { return isset($v['mappings']); })
->then(function($v) { ->then(function($v) {
$v['properties'] = $v['mappings']; $v['properties'] = $v['mappings'];
unset($v['mappings']); unset($v['mappings']);
return $v; return $v;
}) })
->end() ->end()
->beforeNormalization() ->beforeNormalization()
->ifTrue(function ($v) { ->ifTrue(function ($v) {
@ -230,28 +233,10 @@ class Configuration implements ConfigurationInterface
$builder = new TreeBuilder(); $builder = new TreeBuilder();
$node = $builder->root('properties'); $node = $builder->root('properties');
$nestings = $this->getNestings(); $node
$childrenNode = $node
->useAttributeAsKey('name') ->useAttributeAsKey('name')
->prototype('array') ->prototype('variable')
->validate() ->treatNullLike(array());
->always()
->then(function($v) {
foreach (array('fields','properties') as $prop) {
if (isset($v[$prop]) && empty($v[$prop])) {
unset($v[$prop]);
}
}
return $v;
})
->end()
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
$this->addFieldConfig($childrenNode, $nestings);
return $node; return $node;
} }
@ -274,7 +259,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('path_match')->end() ->scalarNode('path_match')->end()
->scalarNode('path_unmatch')->end() ->scalarNode('path_unmatch')->end()
->scalarNode('match_pattern')->end() ->scalarNode('match_pattern')->end()
->append($this->getDynamicTemplateMapping()) ->append($this->getPropertiesNode())
->end() ->end()
->end() ->end()
; ;
@ -282,209 +267,6 @@ class Configuration implements ConfigurationInterface
return $node; return $node;
} }
/**
* @return the array node used for mapping in dynamic templates
*/
protected function getDynamicTemplateMapping()
{
$builder = new TreeBuilder();
$node = $builder->root('mapping');
$nestings = $this->getNestingsForDynamicTemplates();
$this->addFieldConfig($node->children(), $nestings);
return $node;
}
/**
* @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the field config to
* @param array $nestings the nested mappings for the current field level
*/
protected function addFieldConfig($node, $nestings)
{
$node
->scalarNode('type')->defaultValue('string')->end()
->scalarNode('boost')->end()
->scalarNode('store')->end()
->scalarNode('index')->end()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->scalarNode('analyzer')->end()
->scalarNode('term_vector')->end()
->scalarNode('null_value')->end()
->booleanNode('include_in_all')->defaultValue(true)->end()
->booleanNode('enabled')->defaultValue(true)->end()
->scalarNode('lat_lon')->end()
->scalarNode('tree')->end()
->scalarNode('precision')->end()
->scalarNode('tree_levels')->end()
->scalarNode('geohash')->end()
->scalarNode('index_name')->end()
->booleanNode('omit_norms')->end()
->scalarNode('index_options')->end()
->scalarNode('ignore_above')->end()
->scalarNode('position_offset_gap')->end()
->arrayNode('_parent')
->treatNullLike(array())
->children()
->scalarNode('type')->end()
->scalarNode('identifier')->defaultValue('id')->end()
->end()
->end()
->scalarNode('format')->end()
->scalarNode('similarity')->end();
;
if (isset($nestings['fields'])) {
$this->addNestedFieldConfig($node, $nestings, 'fields');
}
if (isset($nestings['properties'])) {
$node
->booleanNode('include_in_parent')->end()
->booleanNode('include_in_root')->end()
;
$this->addNestedFieldConfig($node, $nestings, 'properties');
}
}
/**
* @param \Symfony\Component\Config\Definition\Builder\NodeBuilder $node The node to which to attach the nested config to
* @param array $nestings The nestings for the current field level
* @param string $property the name of the nested property ('fields' or 'properties')
*/
protected function addNestedFieldConfig($node, $nestings, $property)
{
$childrenNode = $node
->arrayNode($property)
->useAttributeAsKey('name')
->prototype('array')
->validate()
->always()
->then(function($v) {
foreach (array('fields','properties') as $prop) {
if (isset($v[$prop]) && empty($v[$prop])) {
unset($v[$prop]);
}
}
return $v;
})
->end()
->treatNullLike(array())
->addDefaultsIfNotSet()
->children();
$this->addFieldConfig($childrenNode, $nestings[$property]);
$childrenNode
->end()
->end()
->end()
;
}
/**
* @return array The unique nested mappings for all types
*/
protected function getNestings()
{
if (!isset($this->configArray[0]['indexes'])) {
return array();
}
$nestings = array();
foreach ($this->configArray[0]['indexes'] as $index) {
if (empty($index['types'])) {
continue;
}
foreach ($index['types'] as $type) {
if (array_key_exists('mappings', $type) and !empty($type['mappings'])) {
$nestings = array_merge_recursive($nestings, $this->getNestingsForType($type['mappings'], $nestings));
}
if (array_key_exists('properties', $type) and !empty($type['properties'])) {
$nestings = array_merge_recursive($nestings, $this->getNestingsForType($type['properties'], $nestings));
}
}
}
return $nestings;
}
/**
* @return array The unique nested mappings for all dynamic templates
*/
protected function getNestingsForDynamicTemplates()
{
if (!isset($this->configArray[0]['indexes'])) {
return array();
}
$nestings = array();
foreach ($this->configArray[0]['indexes'] as $index) {
if (empty($index['types'])) {
continue;
}
foreach ($index['types'] as $type) {
if (empty($type['dynamic_templates'])) {
continue;
}
foreach ($type['dynamic_templates'] as $definition) {
$field = $definition['mapping'];
if (isset($field['fields'])) {
$this->addPropertyNesting($field, $nestings, 'fields');
} else if (isset($field['properties'])) {
$this->addPropertyNesting($field, $nestings, 'properties');
}
}
}
}
return $nestings;
}
/**
* @param array $mappings The mappings for the current type
* @return array The nested mappings defined for this type
*/
protected function getNestingsForType(array $mappings = null)
{
if ($mappings === null) {
return array();
}
$nestings = array();
foreach ($mappings as $field) {
if (isset($field['fields'])) {
$this->addPropertyNesting($field, $nestings, 'fields');
} else if (isset($field['properties'])) {
$this->addPropertyNesting($field, $nestings, 'properties');
}
}
return $nestings;
}
/**
* @param array $field The field mapping definition
* @param array $nestings The nestings array
* @param string $property The nested property name ('fields' or 'properties')
*/
protected function addPropertyNesting($field, &$nestings, $property)
{
if (!isset($nestings[$property])) {
$nestings[$property] = array();
}
$nestings[$property] = array_merge_recursive($nestings[$property], $this->getNestingsForType($field[$property]));
}
/** /**
* Returns the array node used for "_id". * Returns the array node used for "_id".
*/ */
@ -522,7 +304,7 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->scalarNode('compress')->end() ->scalarNode('compress')->end()
->scalarNode('compress_threshold')->end() ->scalarNode('compress_threshold')->end()
->scalarNode('enabled')->end() ->scalarNode('enabled')->defaultTrue()->end()
->end() ->end()
; ;

View file

@ -5,7 +5,6 @@ namespace FOS\ElasticaBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
@ -13,15 +12,32 @@ use InvalidArgumentException;
class FOSElasticaExtension extends Extension class FOSElasticaExtension extends Extension
{ {
protected $indexConfigs = array(); /**
protected $typeFields = array(); * Definition of elastica clients as configured by this extension.
protected $loadedDrivers = array(); *
protected $serializerConfig = array(); * @var array
*/
private $clients = array();
/**
* An array of indexes as configured by the extension.
*
* @var array
*/
private $indexConfigs = array();
/**
* If we've encountered a type mapped to a specific persistence driver, it will be loaded
* here.
*
* @var array
*/
private $loadedDrivers = array();
public function load(array $configs, ContainerBuilder $container) public function load(array $configs, ContainerBuilder $container)
{ {
$configuration = $this->getConfiguration($configs, $container); $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
@ -30,7 +46,9 @@ class FOSElasticaExtension extends Extension
return; return;
} }
$loader->load('config.xml'); foreach (array('config', 'index', 'persister', 'provider', 'source', 'transformer') as $basename) {
$loader->load(sprintf('%s.xml', $basename));
}
if (empty($config['default_client'])) { if (empty($config['default_client'])) {
$keys = array_keys($config['clients']); $keys = array_keys($config['clients']);
@ -42,25 +60,33 @@ class FOSElasticaExtension extends Extension
$config['default_index'] = reset($keys); $config['default_index'] = reset($keys);
} }
$clientIdsByName = $this->loadClients($config['clients'], $container); if (isset($config['serializer'])) {
$this->serializerConfig = isset($config['serializer']) ? $config['serializer'] : null; $loader->load('serializer.xml');
$indexIdsByName = $this->loadIndexes($config['indexes'], $container, $clientIdsByName, $config['default_client']);
$indexRefsByName = array_map(function($id) {
return new Reference($id);
}, $indexIdsByName);
$this->loadIndexManager($indexRefsByName, $container); $this->loadSerializer($config['serializer'], $container);
$this->loadResetter($this->indexConfigs, $container); }
$this->loadClients($config['clients'], $container);
$container->setAlias('fos_elastica.client', sprintf('fos_elastica.client.%s', $config['default_client'])); $container->setAlias('fos_elastica.client', sprintf('fos_elastica.client.%s', $config['default_client']));
$this->loadIndexes($config['indexes'], $container);
$container->setAlias('fos_elastica.index', sprintf('fos_elastica.index.%s', $config['default_index'])); $container->setAlias('fos_elastica.index', sprintf('fos_elastica.index.%s', $config['default_index']));
$container->getDefinition('fos_elastica.config_source.container')->replaceArgument(0, $this->indexConfigs);
$this->loadIndexManager($container);
$this->createDefaultManagerAlias($config['default_manager'], $container); $this->createDefaultManagerAlias($config['default_manager'], $container);
} }
/**
* @param array $config
* @param ContainerBuilder $container
* @return Configuration|null|\Symfony\Component\Config\Definition\ConfigurationInterface
*/
public function getConfiguration(array $config, ContainerBuilder $container) public function getConfiguration(array $config, ContainerBuilder $container)
{ {
return new Configuration($config, $container->getParameter('kernel.debug')); return new Configuration($container->getParameter('kernel.debug'));
} }
/** /**
@ -70,24 +96,26 @@ class FOSElasticaExtension extends Extension
* @param ContainerBuilder $container A ContainerBuilder instance * @param ContainerBuilder $container A ContainerBuilder instance
* @return array * @return array
*/ */
protected function loadClients(array $clients, ContainerBuilder $container) private function loadClients(array $clients, ContainerBuilder $container)
{ {
$clientIds = array();
foreach ($clients as $name => $clientConfig) { foreach ($clients as $name => $clientConfig) {
$clientId = sprintf('fos_elastica.client.%s', $name); $clientId = sprintf('fos_elastica.client.%s', $name);
$clientDef = new Definition('%fos_elastica.client.class%', array($clientConfig));
$clientDef = new DefinitionDecorator('fos_elastica.client_prototype');
$clientDef->replaceArgument(0, $clientConfig);
$logger = $clientConfig['servers'][0]['logger']; $logger = $clientConfig['servers'][0]['logger'];
if (false !== $logger) { if (false !== $logger) {
$clientDef->addMethodCall('setLogger', array(new Reference($logger))); $clientDef->addMethodCall('setLogger', array(new Reference($logger)));
} }
$clientDef->addTag('fos_elastica.client');
$container->setDefinition($clientId, $clientDef); $container->setDefinition($clientId, $clientDef);
$clientIds[$name] = $clientId; $this->clients[$name] = array(
'id' => $clientId,
'reference' => new Reference($clientId)
);
} }
return $clientIds;
} }
/** /**
@ -95,56 +123,44 @@ class FOSElasticaExtension extends Extension
* *
* @param array $indexes An array of indexes configurations * @param array $indexes An array of indexes configurations
* @param ContainerBuilder $container A ContainerBuilder instance * @param ContainerBuilder $container A ContainerBuilder instance
* @param array $clientIdsByName
* @param $defaultClientName
* @param $serializerConfig
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @return array * @return array
*/ */
protected function loadIndexes(array $indexes, ContainerBuilder $container, array $clientIdsByName, $defaultClientName) private function loadIndexes(array $indexes, ContainerBuilder $container)
{ {
$indexIds = array();
foreach ($indexes as $name => $index) { 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];
$indexId = sprintf('fos_elastica.index.%s', $name); $indexId = sprintf('fos_elastica.index.%s', $name);
$indexName = isset($index['index_name']) ? $index['index_name'] : $name; $indexName = isset($index['index_name']) ? $index['index_name']: $name;
$indexDefArgs = array($indexName);
$indexDef = new Definition('%fos_elastica.index.class%', $indexDefArgs); $indexDef = new DefinitionDecorator('fos_elastica.index_prototype');
$indexDef->setFactoryService($clientId); $indexDef->replaceArgument(0, $indexName);
$indexDef->setFactoryMethod('getIndex'); $indexDef->addTag('fos_elastica.index', array(
'name' => $name,
));
if (isset($index['client'])) {
$client = $this->getClient($index['client']);
$indexDef->setFactoryService($client);
}
$container->setDefinition($indexId, $indexDef); $container->setDefinition($indexId, $indexDef);
$typePrototypeConfig = isset($index['type_prototype']) ? $index['type_prototype'] : array(); $reference = new Reference($indexId);
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array( $this->indexConfigs[$name] = array(
'index' => new Reference($indexId), 'elasticsearch_name' => $indexName,
'name_or_alias' => $indexName, 'reference' => $reference,
'config' => array( 'name' => $name,
'properties' => array() 'settings' => $index['settings'],
) 'type_prototype' => isset($index['type_prototype']) ? $index['type_prototype'] : array(),
'use_alias' => $index['use_alias'],
); );
if ($index['finder']) { if ($index['finder']) {
$this->loadIndexFinder($container, $name, $indexId); $this->loadIndexFinder($container, $name, $reference);
}
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
if ($index['use_alias']) {
$this->indexConfigs[$name]['use_alias'] = true;
} }
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig); $this->loadTypes((array) $index['types'], $container, $this->indexConfigs[$name]);
} }
return $indexIds;
} }
/** /**
@ -152,10 +168,10 @@ class FOSElasticaExtension extends Extension
* *
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param string $name The index name * @param string $name The index name
* @param string $indexId The index service identifier * @param Reference $index Reference to the related index
* @return string * @return string
*/ */
protected function loadIndexFinder(ContainerBuilder $container, $name, $indexId) private function loadIndexFinder(ContainerBuilder $container, $name, Reference $index)
{ {
/* Note: transformer services may conflict with "collection.index", if /* Note: transformer services may conflict with "collection.index", if
* an index and type names were "collection" and an index, respectively. * an index and type names were "collection" and an index, respectively.
@ -166,175 +182,141 @@ class FOSElasticaExtension extends Extension
$finderId = sprintf('fos_elastica.finder.%s', $name); $finderId = sprintf('fos_elastica.finder.%s', $name);
$finderDef = new DefinitionDecorator('fos_elastica.finder'); $finderDef = new DefinitionDecorator('fos_elastica.finder');
$finderDef->replaceArgument(0, new Reference($indexId)); $finderDef->replaceArgument(0, $index);
$finderDef->replaceArgument(1, new Reference($transformerId)); $finderDef->replaceArgument(1, new Reference($transformerId));
$container->setDefinition($finderId, $finderDef); $container->setDefinition($finderId, $finderDef);
return $finderId;
} }
/** /**
* Loads the configured types. * Loads the configured types.
* *
* @param array $types An array of types configurations * @param array $types
* @param ContainerBuilder $container A ContainerBuilder instance * @param ContainerBuilder $container
* @param $indexName * @param array $indexConfig
* @param $indexId
* @param array $typePrototypeConfig
* @param $serializerConfig
*/ */
protected function loadTypes(array $types, ContainerBuilder $container, $indexName, $indexId, array $typePrototypeConfig) private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig)
{ {
$indexableCallbacks = array(); $indexableCallbacks = array();
foreach ($types as $name => $type) { foreach ($types as $name => $type) {
$type = self::deepArrayUnion($typePrototypeConfig, $type); $indexName = $indexConfig['name'];
$typeName = sprintf('%s/%s', $indexName, $name);
$typeId = sprintf('%s.%s', $indexId, $name);
$typeDefArgs = array($name);
$typeDef = new Definition('%fos_elastica.type.class%', $typeDefArgs);
$typeDef->setFactoryService($indexId);
$typeDef->setFactoryMethod('getType');
if ($this->serializerConfig) {
$callbackDef = new Definition($this->serializerConfig['callback_class']);
$callbackId = sprintf('%s.%s.serializer.callback', $indexId, $name);
$typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize'))); $typeId = sprintf('%s.%s', $indexConfig['reference'], $name);
$callbackDef->addMethodCall('setSerializer', array(new Reference($this->serializerConfig['serializer']))); $typeDef = new DefinitionDecorator('fos_elastica.type_prototype');
if (isset($type['serializer']['groups'])) { $typeDef->replaceArgument(0, $name);
$callbackDef->addMethodCall('setGroups', array($type['serializer']['groups'])); $typeDef->setFactoryService($indexConfig['reference']);
}
if (isset($type['serializer']['version'])) {
$callbackDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$callbackClassImplementedInterfaces = class_implements($this->serializerConfig['callback_class']); // PHP < 5.4 friendly
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$callbackDef->addMethodCall('setContainer', array(new Reference('service_container')));
}
$container->setDefinition($callbackId, $callbackDef);
$typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize')));
}
$container->setDefinition($typeId, $typeDef); $container->setDefinition($typeId, $typeDef);
$this->indexConfigs[$indexName]['config']['properties'][$name] = array( $typeConfig = array(
"_source" => array("enabled" => true), // Add a default setting for empty mapping settings 'name' => $name,
'mapping' => array(), // An array containing anything that gets sent directly to ElasticSearch
'config' => array(),
); );
if (isset($type['_id'])) { foreach (array(
$this->indexConfigs[$indexName]['config']['properties'][$name]['_id'] = $type['_id']; 'dynamic_templates',
} 'index_analyzer',
if (isset($type['_source'])) { 'properties',
$this->indexConfigs[$indexName]['config']['properties'][$name]['_source'] = $type['_source']; 'search_analyzer',
} '_all',
if (isset($type['_boost'])) { '_boost',
$this->indexConfigs[$indexName]['config']['properties'][$name]['_boost'] = $type['_boost']; '_id',
} '_parent',
if (isset($type['_routing'])) { '_routing',
$this->indexConfigs[$indexName]['config']['properties'][$name]['_routing'] = $type['_routing']; '_source',
} '_timestamp',
if (isset($type['properties']) && !empty($type['properties'])) { '_ttl',
$this->cleanUpProperties($type['properties']); ) as $field) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['properties'] = $type['properties']; if (isset($type[$field])) {
$typeName = sprintf('%s/%s', $indexName, $name); $typeConfig['mapping'][$field] = $type[$field];
$this->typeFields[$typeName] = $type['properties'];
}
if (isset($type['_parent'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['_parent'] = array('type' => $type['_parent']['type']);
$this->typeFields[$typeName]['_parent'] = $type['_parent'];
}
if (isset($type['persistence'])) {
$this->loadTypePersistenceIntegration($type['persistence'], $container, $typeDef, $indexName, $name);
}
if (isset($type['index_analyzer'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['index_analyzer'] = $type['index_analyzer'];
}
if (isset($type['search_analyzer'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['search_analyzer'] = $type['search_analyzer'];
}
if (isset($type['indexable_callback'])) {
$indexableCallbacks[$typeName] = $type['indexable_callback'];
}
if (isset($type['index'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['index'] = $type['index'];
}
if (isset($type['_all'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['_all'] = $type['_all'];
}
if (isset($type['_timestamp'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['_timestamp'] = $type['_timestamp'];
}
if (isset($type['_ttl'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['_ttl'] = $type['_ttl'];
}
if (!empty($type['dynamic_templates'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['dynamic_templates'] = array();
foreach ($type['dynamic_templates'] as $templateName => $templateData) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['dynamic_templates'][] = array($templateName => $templateData);
} }
} }
foreach (array(
'persistence',
'serializer'
) as $field) {
$typeConfig['config'][$field] = array_key_exists($field, $type) ?
$type[$field] :
null;
}
$this->indexConfigs[$indexName]['types'][$name] = $typeConfig;
if (isset($type['persistence'])) {
$this->loadTypePersistenceIntegration($type['persistence'], $container, new Reference($typeId), $indexName, $name);
$typeConfig['persistence'] = $type['persistence'];
}
if (isset($type['indexable_callback'])) {
$indexableCallbacks[sprintf('%s/%s', $indexName, $name)] = $type['indexable_callback'];
}
if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$typeSerializerId = sprintf('%s.serializer.callback', $typeId);
$typeSerializerDef = new DefinitionDecorator('fos_elastica.serializer_callback_prototype');
if (isset($type['serializer']['groups'])) {
$typeSerializerDef->addMethodCall('setGroups', array($type['serializer']['groups']));
}
if (isset($type['serializer']['version'])) {
$typeSerializerDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$typeDef->addMethodCall('setSerializer', array(array(new Reference($typeSerializerId), 'serialize')));
$container->setDefinition($typeSerializerId, $typeSerializerDef);
}
} }
$indexable = $container->getDefinition('fos_elastica.indexable'); $indexable = $container->getDefinition('fos_elastica.indexable');
$indexable->replaceArgument(0, $indexableCallbacks); $indexable->replaceArgument(0, $indexableCallbacks);
} }
/**
* Merges two arrays without reindexing numeric keys.
*
* @param array $array1 An array to merge
* @param array $array2 An array to merge
*
* @return array The merged array
*/
static protected function deepArrayUnion($array1, $array2)
{
foreach ($array2 as $key => $value) {
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
$array1[$key] = self::deepArrayUnion($array1[$key], $value);
} else {
$array1[$key] = $value;
}
}
return $array1;
}
/** /**
* Loads the optional provider and finder for a type * Loads the optional provider and finder for a type
* *
* @param array $typeConfig * @param array $typeConfig
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container * @param ContainerBuilder $container
* @param \Symfony\Component\DependencyInjection\Definition $typeDef * @param Reference $typeRef
* @param $indexName * @param string $indexName
* @param $typeName * @param string $typeName
*/ */
protected function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Definition $typeDef, $indexName, $typeName) private function loadTypePersistenceIntegration(array $typeConfig, ContainerBuilder $container, Reference $typeRef, $indexName, $typeName)
{ {
$this->loadDriver($container, $typeConfig['driver']); $this->loadDriver($container, $typeConfig['driver']);
$elasticaToModelTransformerId = $this->loadElasticaToModelTransformer($typeConfig, $container, $indexName, $typeName); $elasticaToModelTransformerId = $this->loadElasticaToModelTransformer($typeConfig, $container, $indexName, $typeName);
$modelToElasticaTransformerId = $this->loadModelToElasticaTransformer($typeConfig, $container, $indexName, $typeName); $modelToElasticaTransformerId = $this->loadModelToElasticaTransformer($typeConfig, $container, $indexName, $typeName);
$objectPersisterId = $this->loadObjectPersister($typeConfig, $typeDef, $container, $indexName, $typeName, $modelToElasticaTransformerId); $objectPersisterId = $this->loadObjectPersister($typeConfig, $typeRef, $container, $indexName, $typeName, $modelToElasticaTransformerId);
if (isset($typeConfig['provider'])) { if (isset($typeConfig['provider'])) {
$this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); $this->loadTypeProvider($typeConfig, $container, $objectPersisterId, $indexName, $typeName);
} }
if (isset($typeConfig['finder'])) { if (isset($typeConfig['finder'])) {
$this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeDef, $indexName, $typeName); $this->loadTypeFinder($typeConfig, $container, $elasticaToModelTransformerId, $typeRef, $indexName, $typeName);
} }
if (isset($typeConfig['listener'])) { if (isset($typeConfig['listener'])) {
$this->loadTypeListener($typeConfig, $container, $objectPersisterId, $typeDef, $indexName, $typeName); $this->loadTypeListener($typeConfig, $container, $objectPersisterId, $indexName, $typeName);
} }
} }
protected function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) /**
* Creates and loads an ElasticaToModelTransformer.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
* @return string
*/
private function loadElasticaToModelTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
{ {
if (isset($typeConfig['elastica_to_model_transformer']['service'])) { if (isset($typeConfig['elastica_to_model_transformer']['service'])) {
return $typeConfig['elastica_to_model_transformer']['service']; return $typeConfig['elastica_to_model_transformer']['service'];
} }
/* Note: transformer services may conflict with "prototype.driver", if /* Note: transformer services may conflict with "prototype.driver", if
* the index and type names were "prototype" and a driver, respectively. * the index and type names were "prototype" and a driver, respectively.
*/ */
@ -347,28 +329,32 @@ class FOSElasticaExtension extends Extension
$argPos = ('propel' === $typeConfig['driver']) ? 0 : 1; $argPos = ('propel' === $typeConfig['driver']) ? 0 : 1;
$serviceDef->replaceArgument($argPos, $typeConfig['model']); $serviceDef->replaceArgument($argPos, $typeConfig['model']);
$serviceDef->replaceArgument($argPos + 1, array( $serviceDef->replaceArgument($argPos + 1, array_merge($typeConfig['elastica_to_model_transformer'], array(
'hydrate' => $typeConfig['elastica_to_model_transformer']['hydrate'], 'identifier' => $typeConfig['identifier'],
'identifier' => $typeConfig['identifier'], )));
'ignore_missing' => $typeConfig['elastica_to_model_transformer']['ignore_missing'],
'query_builder_method' => $typeConfig['elastica_to_model_transformer']['query_builder_method']
));
$container->setDefinition($serviceId, $serviceDef); $container->setDefinition($serviceId, $serviceDef);
return $serviceId; return $serviceId;
} }
protected function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName) /**
* Creates and loads a ModelToElasticaTransformer for an index/type.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
* @return string
*/
private function loadModelToElasticaTransformer(array $typeConfig, ContainerBuilder $container, $indexName, $typeName)
{ {
if (isset($typeConfig['model_to_elastica_transformer']['service'])) { if (isset($typeConfig['model_to_elastica_transformer']['service'])) {
return $typeConfig['model_to_elastica_transformer']['service']; return $typeConfig['model_to_elastica_transformer']['service'];
} }
if ($this->serializerConfig) { $abstractId = $container->hasDefinition('fos_elastica.serializer_callback_prototype') ?
$abstractId = sprintf('fos_elastica.model_to_elastica_identifier_transformer'); 'fos_elastica.model_to_elastica_identifier_transformer' :
} else { 'fos_elastica.model_to_elastica_transformer';
$abstractId = sprintf('fos_elastica.model_to_elastica_transformer');
}
$serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName); $serviceId = sprintf('fos_elastica.model_to_elastica_transformer.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId); $serviceDef = new DefinitionDecorator($abstractId);
@ -380,22 +366,34 @@ class FOSElasticaExtension extends Extension
return $serviceId; return $serviceId;
} }
protected function loadObjectPersister(array $typeConfig, Definition $typeDef, ContainerBuilder $container, $indexName, $typeName, $transformerId) /**
* Creates and loads an object persister for a type.
*
* @param array $typeConfig
* @param Reference $typeRef
* @param ContainerBuilder $container
* @param string $indexName
* @param string $typeName
* @param string $transformerId
* @return string
*/
private function loadObjectPersister(array $typeConfig, Reference $typeRef, ContainerBuilder $container, $indexName, $typeName, $transformerId)
{ {
$arguments = array( $arguments = array(
$typeDef, $typeRef,
new Reference($transformerId), new Reference($transformerId),
$typeConfig['model'], $typeConfig['model'],
); );
if ($this->serializerConfig) { if ($container->hasDefinition('fos_elastica.serializer_callback_prototype')) {
$abstractId = 'fos_elastica.object_serializer_persister'; $abstractId = 'fos_elastica.object_serializer_persister';
$callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['index'], $typeName); $callbackId = sprintf('%s.%s.serializer.callback', $this->indexConfigs[$indexName]['reference'], $typeName);
$arguments[] = array(new Reference($callbackId), 'serialize'); $arguments[] = array(new Reference($callbackId), 'serialize');
} else { } else {
$abstractId = 'fos_elastica.object_persister'; $abstractId = 'fos_elastica.object_persister';
$arguments[] = $this->typeFields[sprintf('%s/%s', $indexName, $typeName)]; $arguments[] = $this->indexConfigs[$indexName]['types'][$typeName]['mapping']['properties'];
} }
$serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName); $serviceId = sprintf('fos_elastica.object_persister.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId); $serviceDef = new DefinitionDecorator($abstractId);
foreach ($arguments as $i => $argument) { foreach ($arguments as $i => $argument) {
@ -407,11 +405,22 @@ class FOSElasticaExtension extends Extension
return $serviceId; return $serviceId;
} }
protected function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName) /**
* Loads a provider for a type.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $objectPersisterId
* @param string $indexName
* @param string $typeName
* @return string
*/
private function loadTypeProvider(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
{ {
if (isset($typeConfig['provider']['service'])) { if (isset($typeConfig['provider']['service'])) {
return $typeConfig['provider']['service']; return $typeConfig['provider']['service'];
} }
/* Note: provider services may conflict with "prototype.driver", if the /* Note: provider services may conflict with "prototype.driver", if the
* index and type names were "prototype" and a driver, respectively. * index and type names were "prototype" and a driver, respectively.
*/ */
@ -430,11 +439,22 @@ class FOSElasticaExtension extends Extension
return $providerId; return $providerId;
} }
protected function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $typeDef, $indexName, $typeName) /**
* Loads doctrine listeners to handle indexing of new or updated objects.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param string $objectPersisterId
* @param string $indexName
* @param string $typeName
* @return string
*/
private function loadTypeListener(array $typeConfig, ContainerBuilder $container, $objectPersisterId, $indexName, $typeName)
{ {
if (isset($typeConfig['listener']['service'])) { if (isset($typeConfig['listener']['service'])) {
return $typeConfig['listener']['service']; return $typeConfig['listener']['service'];
} }
/* Note: listener services may conflict with "prototype.driver", if the /* Note: listener services may conflict with "prototype.driver", if the
* index and type names were "prototype" and a driver, respectively. * index and type names were "prototype" and a driver, respectively.
*/ */
@ -496,14 +516,25 @@ class FOSElasticaExtension extends Extension
return $events; return $events;
} }
protected function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, $typeDef, $indexName, $typeName) /**
* Loads a Type specific Finder.
*
* @param array $typeConfig
* @param ContainerBuilder $container
* @param $elasticaToModelId
* @param Reference $typeRef
* @param string $indexName
* @param string $typeName
* @return string
*/
private function loadTypeFinder(array $typeConfig, ContainerBuilder $container, $elasticaToModelId, Reference $typeRef, $indexName, $typeName)
{ {
if (isset($typeConfig['finder']['service'])) { if (isset($typeConfig['finder']['service'])) {
$finderId = $typeConfig['finder']['service']; $finderId = $typeConfig['finder']['service'];
} else { } else {
$finderId = sprintf('fos_elastica.finder.%s.%s', $indexName, $typeName); $finderId = sprintf('fos_elastica.finder.%s.%s', $indexName, $typeName);
$finderDef = new DefinitionDecorator('fos_elastica.finder'); $finderDef = new DefinitionDecorator('fos_elastica.finder');
$finderDef->replaceArgument(0, $typeDef); $finderDef->replaceArgument(0, $typeRef);
$finderDef->replaceArgument(1, new Reference($elasticaToModelId)); $finderDef->replaceArgument(1, new Reference($elasticaToModelId));
$container->setDefinition($finderId, $finderDef); $container->setDefinition($finderId, $finderDef);
} }
@ -522,39 +553,61 @@ class FOSElasticaExtension extends Extension
/** /**
* Loads the index manager * Loads the index manager
* *
* @param array $indexRefsByName
* @param ContainerBuilder $container * @param ContainerBuilder $container
**/ **/
protected function loadIndexManager(array $indexRefsByName, ContainerBuilder $container) private function loadIndexManager(ContainerBuilder $container)
{ {
$indexRefs = array_map(function ($index) {
return $index['reference'];
}, $this->indexConfigs);
$managerDef = $container->getDefinition('fos_elastica.index_manager'); $managerDef = $container->getDefinition('fos_elastica.index_manager');
$managerDef->replaceArgument(0, $indexRefsByName); $managerDef->replaceArgument(0, $indexRefs);
$managerDef->replaceArgument(1, new Reference('fos_elastica.index'));
} }
/** /**
* Loads the resetter * Makes sure a specific driver has been loaded.
* *
* @param array $indexConfigs * @param ContainerBuilder $container
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container * @param string $driver
*/ */
protected function loadResetter(array $indexConfigs, ContainerBuilder $container) private function loadDriver(ContainerBuilder $container, $driver)
{
$resetterDef = $container->getDefinition('fos_elastica.resetter');
$resetterDef->replaceArgument(0, $indexConfigs);
}
protected function loadDriver(ContainerBuilder $container, $driver)
{ {
if (in_array($driver, $this->loadedDrivers)) { if (in_array($driver, $this->loadedDrivers)) {
return; return;
} }
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load($driver.'.xml'); $loader->load($driver.'.xml');
$this->loadedDrivers[] = $driver; $this->loadedDrivers[] = $driver;
} }
protected function createDefaultManagerAlias($defaultManager, ContainerBuilder $container) /**
* Loads and configures the serializer prototype.
*
* @param array $config
* @param ContainerBuilder $container
*/
private function loadSerializer($config, ContainerBuilder $container)
{
$container->setAlias('fos_elastica.serializer', $config['serializer']);
$serializer = $container->getDefinition('fos_elastica.serializer_callback_prototype');
$serializer->setClass($config['callback_class']);
$callbackClassImplementedInterfaces = class_implements($config['callback_class']);
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$serializer->addMethodCall('setContainer', array(new Reference('service_container')));
}
}
/**
* Creates a default manager alias for defined default manager or the first loaded driver.
*
* @param string $defaultManager
* @param ContainerBuilder $container
*/
private function createDefaultManagerAlias($defaultManager, ContainerBuilder $container)
{ {
if (0 == count($this->loadedDrivers)) { if (0 == count($this->loadedDrivers)) {
return; return;
@ -571,18 +624,19 @@ class FOSElasticaExtension extends Extension
$container->setAlias('fos_elastica.manager', sprintf('fos_elastica.manager.%s', $defaultManagerService)); $container->setAlias('fos_elastica.manager', sprintf('fos_elastica.manager.%s', $defaultManagerService));
} }
protected function cleanUpProperties(&$properties) /**
* Returns a reference to a client given its configured name.
*
* @param string $clientName
* @return Reference
* @throws \InvalidArgumentException
*/
private function getClient($clientName)
{ {
foreach ($properties as &$fieldProperties) { if (!array_key_exists($clientName, $this->clients)) {
if (empty($fieldProperties['fields'])) { throw new InvalidArgumentException(sprintf('The elastica client with name "%s" is not defined', $clientName));
unset($fieldProperties['fields']);
} else {
$this->cleanUpProperties($fieldProperties['fields']);
}
if (!empty($fieldProperties['properties'])) {
$this->cleanUpProperties($fieldProperties['properties']);
}
} }
return $this->clients[$clientName]['reference'];
} }
} }

View file

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

58
Elastica/Client.php Normal file
View file

@ -0,0 +1,58 @@
<?php
namespace FOS\ElasticaBundle\Elastica;
use Elastica\Client as BaseClient;
use Elastica\Request;
use FOS\ElasticaBundle\Logger\ElasticaLogger;
/**
* Extends the default Elastica client to provide logging for errors that occur
* during communication with ElasticSearch.
*
* @author Gordon Franke <info@nevalon.de>
*/
class Client extends BaseClient
{
/**
* Stores created indexes to avoid recreation.
*
* @var array
*/
private $indexCache = array();
/**
* {@inheritdoc}
*/
public function request($path, $method = Request::GET, $data = array(), array $query = array())
{
$start = microtime(true);
$response = parent::request($path, $method, $data, $query);
if ($this->_logger and $this->_logger instanceof ElasticaLogger) {
$time = microtime(true) - $start;
$connection = $this->getLastRequest()->getConnection();
$connection_array = array(
'host' => $connection->getHost(),
'port' => $connection->getPort(),
'transport' => $connection->getTransport(),
'headers' => $connection->hasConfig('headers') ? $connection->getConfig('headers') : array(),
);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
}
return $response;
}
public function getIndex($name)
{
if (isset($this->indexCache[$name])) {
return $this->indexCache[$name];
}
return $this->indexCache[$name] = new Index($this, $name);
}
}

59
Elastica/Index.php Normal file
View file

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

View file

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

134
Index/AliasProcessor.php Normal file
View file

@ -0,0 +1,134 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Index;
use Elastica\Exception\ExceptionInterface;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Elastica\Client;
use FOS\ElasticaBundle\Elastica\Index;
class AliasProcessor
{
/**
* Sets the randomised root name for an index.
*
* @param IndexConfig $indexConfig
* @param Index $index
*/
public function setRootName(IndexConfig $indexConfig, Index $index)
{
$index->overrideName(sprintf('%s_%s', $indexConfig->getElasticSearchName(), uniqid()));
}
/**
* Switches an index to become the new target for an alias. Only applies for
* indexes that are set to use aliases.
*
* @param IndexConfig $indexConfig
* @param Index $index
* @throws \RuntimeException
*/
public function switchIndexAlias(IndexConfig $indexConfig, Index $index)
{
$aliasName = $indexConfig->getElasticSearchName();
$oldIndexName = false;
$newIndexName = $index->getName();
$aliasedIndexes = $this->getAliasedIndexes($aliasName);
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(
sprintf(
'Alias %s is used for multiple indexes: [%s].
Make sure it\'s either not used or is assigned to one index only',
$aliasName,
join(', ', $aliasedIndexes)
)
);
}
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) == 1) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName)
);
try {
$this->client->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$index->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getMessage()
);
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
), 0, $renameAliasException
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($this->client, $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
), 0, $deleteOldIndexException
);
}
}
}
/**
* Returns array of indexes which are mapped to given alias
*
* @param string $aliasName Alias name
* @return array
*/
private function getAliasedIndexes($aliasName)
{
$aliasesInfo = $this->client->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
return $aliasedIndexes;
}
}

63
Index/IndexManager.php Normal file
View file

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

114
Index/MappingBuilder.php Normal file
View file

@ -0,0 +1,114 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Index;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Configuration\TypeConfig;
class MappingBuilder
{
/**
* Builds mappings for an entire index.
*
* @param IndexConfig $indexConfig
* @return array
*/
public function buildIndexMapping(IndexConfig $indexConfig)
{
$typeMappings = array();
foreach ($indexConfig->getTypes() as $typeConfig) {
$typeMappings[$typeConfig->getName()] = $this->buildTypeMapping($typeConfig);
}
$mapping = array(
'mappings' => $typeMappings,
'settings' => $indexConfig->getSettings(),
// 'warmers' => $indexConfig->getWarmers(),
);
return $mapping;
}
/**
* Builds mappings for a single type.
*
* @param TypeConfig $typeConfig
* @return array
*/
public function buildTypeMapping(TypeConfig $typeConfig)
{
$mapping = array_merge($typeConfig->getMapping(), array(
// 'date_detection' => true,
// 'dynamic_date_formats' => array()
// 'dynamic_templates' => $typeConfig->getDynamicTemplates(),
// 'index_analyzer' => $typeConfig->getIndexAnalyzer(),
// 'numeric_detection' => false,
// 'properties' => array(),
// 'search_analyzer' => $typeConfig->getSearchAnalyzer(),
));
$this->fixProperties($mapping['properties']);
if ($typeConfig->getModel()) {
$mapping['_meta']['model'] = $typeConfig->getModel();
}
return $mapping;
}
/**
* create type mapping object
*
* @param array $indexConfig
* @return Mapping
*/
protected function createMapping($indexConfig)
{
/*$mapping = $this->createMapping($indexConfig['config']['properties'][$typeName]);*/
$mapping = Mapping::create($indexConfig['properties']);
$mappingSpecialFields = array('_uid', '_id', '_source', '_all', '_analyzer', '_boost', '_routing', '_index', '_size', '_timestamp', '_ttl', 'dynamic_templates');
foreach ($mappingSpecialFields as $specialField) {
if (isset($indexConfig[$specialField])) {
$mapping->setParam($specialField, $indexConfig[$specialField]);
}
}
if (isset($indexConfig['_parent'])) {
$mapping->setParam('_parent', array('type' => $indexConfig['_parent']['type']));
}
return $mapping;
}
/**
* Fixes any properties and applies basic defaults for any field that does not have
* required options.
*
* @param $properties
*/
private function fixProperties(&$properties)
{
foreach ($properties as $name => &$property) {
if (!isset($property['type'])) {
$property['type'] = 'string';
}
if (!isset($property['store'])) {
$property['store'] = true;
}
if (isset($property['properties'])) {
$this->fixProperties($property['properties']);
}
}
}
}

123
Index/Resetter.php Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <services>
<service id="fos_elastica.provider.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true"> <service id="fos_elastica.provider.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true">
<argument /> <!-- object persister --> <argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
@ -14,15 +13,16 @@
<argument type="service" id="doctrine_mongodb" /> <argument type="service" id="doctrine_mongodb" />
</service> </service>
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false"> <service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false" abstract="true">
<argument /> <!-- object persister --> <argument type="service" /> <!-- object persister -->
<argument type="collection" /> <!-- events --> <argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
<argument/> <!-- identifier --> <argument type="collection" /> <!-- configuration -->
<argument /> <!-- logger --> <argument type="service" on-invalid="null" /> <!-- logger -->
<!-- <tag name="doctrine_mongodb.odm.event_subscriber" /> Bug in doctrine bridge forbids this: https://github.com/symfony/symfony/pull/11160 -->
</service> </service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false"> <service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false" abstract="true">
<argument type="service" id="doctrine_mongodb" /> <argument type="service" id="doctrine_mongodb" />
<argument /> <!-- model --> <argument /> <!-- model -->
<argument type="collection" /> <!-- options --> <argument type="collection" /> <!-- options -->
@ -35,7 +35,5 @@
<argument type="service" id="doctrine_mongodb"/> <argument type="service" id="doctrine_mongodb"/>
<argument type="service" id="annotation_reader"/> <argument type="service" id="annotation_reader"/>
</service> </service>
</services> </services>
</container> </container>

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services" <container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <services>
<service id="fos_elastica.provider.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true"> <service id="fos_elastica.provider.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true">
<argument /> <!-- object persister --> <argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
@ -14,15 +13,16 @@
<argument type="service" id="doctrine" /> <argument type="service" id="doctrine" />
</service> </service>
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false"> <service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false" abstract="true">
<argument /> <!-- object persister --> <argument type="service" /> <!-- object persister -->
<argument type="collection" /> <!-- events --> <argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- identifier --> <argument type="collection" /> <!-- configuration -->
<argument /> <!-- logger --> <argument type="service" on-invalid="null" /> <!-- logger -->
<!-- <tag name="doctrine.event_subscriber" /> Bug in doctrine bridge forbids this: https://github.com/symfony/symfony/pull/11160 -->
</service> </service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer" public="false"> <service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer" public="false" abstract="true">
<argument type="service" id="doctrine" /> <argument type="service" id="doctrine" />
<argument /> <!-- model --> <argument /> <!-- model -->
<argument type="collection" /> <!-- options --> <argument type="collection" /> <!-- options -->
@ -32,10 +32,8 @@
</service> </service>
<service id="fos_elastica.manager.orm" class="FOS\ElasticaBundle\Doctrine\RepositoryManager"> <service id="fos_elastica.manager.orm" class="FOS\ElasticaBundle\Doctrine\RepositoryManager">
<argument type="service" id="doctrine"/> <argument type="service" id="doctrine" />
<argument type="service" id="annotation_reader"/> <argument type="service" id="annotation_reader" />
</service> </service>
</services> </services>
</container> </container>

View file

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

View file

@ -4,7 +4,6 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services> <services>
<service id="fos_elastica.provider.prototype.propel" class="FOS\ElasticaBundle\Propel\Provider" public="true" abstract="true"> <service id="fos_elastica.provider.prototype.propel" class="FOS\ElasticaBundle\Propel\Provider" public="true" abstract="true">
<argument /> <!-- object persister --> <argument /> <!-- object persister -->
<argument type="service" id="fos_elastica.indexable" /> <argument type="service" id="fos_elastica.indexable" />
@ -23,7 +22,5 @@
<service id="fos_elastica.manager.propel" class="%fos_elastica.manager.class%"> <service id="fos_elastica.manager.propel" class="%fos_elastica.manager.class%">
<argument type="service" id="annotation_reader"/> <argument type="service" id="annotation_reader"/>
</service> </service>
</services> </services>
</container> </container>

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
private function getConfigs(array $configArray) private function getConfigs(array $configArray)
{ {
$configuration = new Configuration(array($configArray), true); $configuration = new Configuration(true);
return $this->processor->processConfiguration($configuration, array($configArray)); return $this->processor->processConfiguration($configuration, array($configArray));
} }
@ -118,7 +118,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
public function testTypeConfig() public function testTypeConfig()
{ {
$configuration = $this->getConfigs(array( $this->getConfigs(array(
'clients' => array( 'clients' => array(
'default' => array('url' => 'http://localhost:9200'), 'default' => array('url' => 'http://localhost:9200'),
), ),
@ -159,65 +159,6 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
) )
) )
)); ));
$this->assertEquals('string', $configuration['indexes']['test']['types']['test']['properties']['title']['type']);
$this->assertTrue($configuration['indexes']['test']['types']['test']['properties']['title']['include_in_all']);
}
public function testEmptyPropertiesIndexIsUnset()
{
$config = array(
'indexes' => array(
'test' => array(
'types' => array(
'test' => array(
'mappings' => array(
'title' => array(
'type' => 'string',
'fields' => array(
'autocomplete' => null
)
),
'content' => null,
'children' => array(
'type' => 'object',
'properties' => array(
'title' => array(
'type' => 'string',
'fields' => array(
'autocomplete' => null
)
),
'content' => null,
'tags' => array(
'properties' => array(
'tag' => array(
'type' => 'string',
'index' => 'not_analyzed'
)
)
)
)
),
)
)
)
)
)
);
$processor = new Processor();
$configuration = $processor->processConfiguration(new Configuration(array($config), false), array($config));
$mapping = $configuration['indexes']['test']['types']['test']['properties'];
$this->assertArrayNotHasKey('properties', $mapping['content']);
$this->assertArrayNotHasKey('properties', $mapping['title']);
$this->assertArrayHasKey('properties', $mapping['children']);
$this->assertArrayNotHasKey('properties', $mapping['children']['properties']['title']);
$this->assertArrayNotHasKey('properties', $mapping['children']['properties']['content']);
$this->assertArrayHasKey('properties', $mapping['children']['properties']['tags']);
$this->assertArrayNotHasKey('properties', $mapping['children']['properties']['tags']['properties']['tag']);
} }
public function testClientConfigurationNoUrl() public function testClientConfigurationNoUrl()
@ -260,7 +201,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
public function testNestedProperties() public function testNestedProperties()
{ {
$configuration = $this->getConfigs(array( $this->getConfigs(array(
'clients' => array( 'clients' => array(
'default' => array('url' => 'http://localhost:9200'), 'default' => array('url' => 'http://localhost:9200'),
), ),

View file

@ -1,11 +1,11 @@
<?php <?php
namespace FOS\ElasticaBundle\Tests\Resetter; namespace FOS\ElasticaBundle\Tests\Client;
use Elastica\Request; use Elastica\Request;
use Elastica\Transport\Null as NullTransport; use Elastica\Transport\Null as NullTransport;
class ClientTest extends \PHPUnit_Framework_TestCase class LoggingClientTest extends \PHPUnit_Framework_TestCase
{ {
public function testRequestsAreLogged() public function testRequestsAreLogged()
{ {
@ -28,7 +28,7 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$this->isType('array') $this->isType('array')
); );
$client = $this->getMockBuilder('FOS\ElasticaBundle\Client') $client = $this->getMockBuilder('FOS\ElasticaBundle\Elastica\Client')
->setMethods(array('getConnection')) ->setMethods(array('getConnection'))
->getMock(); ->getMock();

View file

@ -9,31 +9,16 @@ class FOSElasticaBundleTest extends \PHPUnit_Framework_TestCase
{ {
public function testCompilerPassesAreRegistered() public function testCompilerPassesAreRegistered()
{ {
$passes = array(
array (
'FOS\ElasticaBundle\DependencyInjection\Compiler\RegisterProvidersPass',
PassConfig::TYPE_BEFORE_REMOVING
),
array (
'FOS\ElasticaBundle\DependencyInjection\Compiler\TransformerPass'
),
);
$container = $this $container = $this
->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); ->getMock('Symfony\Component\DependencyInjection\ContainerBuilder');
$container $container
->expects($this->at(0)) ->expects($this->atLeastOnce())
->method('addCompilerPass') ->method('addCompilerPass')
->with($this->isInstanceOf($passes[0][0]), $passes[0][1]); ->with($this->isInstanceOf('Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface'));
$container
->expects($this->at(1))
->method('addCompilerPass')
->with($this->isInstanceOf($passes[1][0]));
$bundle = new FOSElasticaBundle(); $bundle = new FOSElasticaBundle();
$bundle->build($container); $bundle->build($container);
} }
} }

View file

@ -0,0 +1,58 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace FOS\ElasticaBundle\Tests\Functional;
use Symfony\Bundle\FrameworkBundle\Client;
/**
* @group functional
*/
class ConfigurationManagerTest extends WebTestCase
{
public function testContainerSource()
{
$client = $this->createClient(array('test_case' => 'Basic'));
$manager = $this->getManager($client);
$index = $manager->getIndexConfiguration('index');
$this->assertEquals('index', $index->getName());
$this->assertCount(2, $index->getTypes());
$this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('type'));
$this->assertInstanceOf('FOS\\ElasticaBundle\\Configuration\\TypeConfig', $index->getType('parent'));
}
protected function setUp()
{
parent::setUp();
$this->deleteTmpDir('Basic');
}
protected function tearDown()
{
parent::tearDown();
$this->deleteTmpDir('Basic');
}
/**
* @param Client $client
* @return \FOS\ElasticaBundle\Configuration\ConfigManager
*/
private function getManager(Client $client)
{
$manager = $client->getContainer()->get('fos_elastica.config_manager');
return $manager;
}
}

View file

@ -3,9 +3,11 @@
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use FOS\ElasticaBundle\FOSElasticaBundle; use FOS\ElasticaBundle\FOSElasticaBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use JMS\SerializerBundle\JMSSerializerBundle;
return array( return array(
new FrameworkBundle(), new FrameworkBundle(),
new FOSElasticaBundle(), new FOSElasticaBundle(),
new DoctrineBundle() new DoctrineBundle(),
new JMSSerializerBundle(),
); );

View file

@ -13,7 +13,13 @@ fos_elastica:
clients: clients:
default: default:
url: http://localhost:9200 url: http://localhost:9200
serializer: ~
indexes: indexes:
fos_elastica_test:
types:
type:
properties:
field1: ~
index: index:
index_name: foselastica_test_%kernel.environment% index_name: foselastica_test_%kernel.environment%
types: types:
@ -25,6 +31,9 @@ fos_elastica:
model: FOS\ElasticaBundle\Tests\Functional\TypeObj model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener: listener:
is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()' is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()'
serializer:
groups: ['search']
version: 1.1
type2: type2:
properties: properties:
field1: ~ field1: ~

View file

@ -0,0 +1,59 @@
<?php
namespace FOS\ElasticaBundle\Tests\Index;
use FOS\ElasticaBundle\Index\IndexManager;
class IndexManagerTest extends \PHPUnit_Framework_TestCase
{
private $indexes = array();
/**
* @var IndexManager
*/
private $indexManager;
public function setUp()
{
foreach (array('index1', 'index2', 'index3') as $indexName) {
$index = $this->getMockBuilder('FOS\\ElasticaBundle\\Elastica\\Index')
->disableOriginalConstructor()
->getMock();
$index->expects($this->any())
->method('getName')
->will($this->returnValue($indexName));
$this->indexes[$indexName] = $index;
}
$this->indexManager = new IndexManager($this->indexes, $this->indexes['index2']);
}
public function testGetAllIndexes()
{
$this->assertEquals($this->indexes, $this->indexManager->getAllIndexes());
}
public function testGetIndex()
{
$this->assertEquals($this->indexes['index1'], $this->indexManager->getIndex('index1'));
$this->assertEquals($this->indexes['index2'], $this->indexManager->getIndex('index2'));
$this->assertEquals($this->indexes['index3'], $this->indexManager->getIndex('index3'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetIndexShouldThrowExceptionForInvalidName()
{
$this->indexManager->getIndex('index4');
}
public function testGetDefaultIndex()
{
$this->assertEquals('index2', $this->indexManager->getIndex()->getName());
$this->assertEquals('index2', $this->indexManager->getDefaultIndex()->getName());
}
}

View file

@ -1,20 +1,45 @@
<?php <?php
namespace FOS\ElasticaBundle\Tests\Resetter; namespace FOS\ElasticaBundle\Tests\Index;
use Elastica\Exception\ResponseException; use Elastica\Exception\ResponseException;
use Elastica\Request; use Elastica\Request;
use Elastica\Response; use Elastica\Response;
use FOS\ElasticaBundle\Resetter;
use Elastica\Type\Mapping; use Elastica\Type\Mapping;
use FOS\ElasticaBundle\Configuration\IndexConfig;
use FOS\ElasticaBundle\Index\Resetter;
class ResetterTest extends \PHPUnit_Framework_TestCase class ResetterTest extends \PHPUnit_Framework_TestCase
{ {
private $indexConfigsByName; /**
* @var Resetter
*/
private $resetter;
private $configManager;
private $indexManager;
private $aliasProcessor;
private $mappingBuilder;
public function setUp() public function setUp()
{ {
$this->indexConfigsByName = array( $this->markTestIncomplete('To be rewritten');
$this->configManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Configuration\\ConfigManager')
->disableOriginalConstructor()
->getMock();
$this->indexManager = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\IndexManager')
->disableOriginalConstructor()
->getMock();
$this->aliasProcessor = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\AliasProcessor')
->disableOriginalConstructor()
->getMock();
$this->mappingBuilder = $this->getMockBuilder('FOS\\ElasticaBundle\\Index\\MappingBuilder')
->disableOriginalConstructor()
->getMock();
$this->resetter = new Resetter($this->configManager, $this->indexManager, $this->aliasProcessor, $this->mappingBuilder);
/*$this->indexConfigsByName = array(
'foo' => array( 'foo' => array(
'index' => $this->getMockElasticaIndex(), 'index' => $this->getMockElasticaIndex(),
'config' => array( 'config' => array(
@ -54,12 +79,26 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
), ),
), ),
), ),
); );*/
} }
public function testResetAllIndexes() public function testResetAllIndexes()
{ {
$this->indexConfigsByName['foo']['index']->expects($this->once()) $this->configManager->expects($this->once())
->method('getIndexNames')
->will($this->returnValue(array('index1')));
$this->configManager->expects($this->once())
->method('getIndexConfiguration')
->with('index1')
->will($this->returnValue(new IndexConfig('index1', array(), array())));
$this->indexManager->expects($this->once())
->method('getIndex')
->with('index1')
->will($this->returnValue());
/*$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('create') ->method('create')
->with($this->indexConfigsByName['foo']['config'], true); ->with($this->indexConfigsByName['foo']['config'], true);
@ -67,8 +106,8 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
->method('create') ->method('create')
->with($this->indexConfigsByName['bar']['config'], true); ->with($this->indexConfigsByName['bar']['config'], true);
$resetter = new Resetter($this->indexConfigsByName); $resetter = new Resetter($this->indexConfigsByName);*/
$resetter->resetAllIndexes(); $this->resetter->resetAllIndexes();
} }
public function testResetIndex() public function testResetIndex()

View file

@ -1,58 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Tests\IndexManager;
use FOS\ElasticaBundle\IndexManager;
class IndexManagerTest extends \PHPUnit_Framework_TestCase
{
private $defaultIndexName;
private $indexesByName;
/** @var IndexManager */
private $indexManager;
public function setUp()
{
$this->defaultIndexName = 'index2';
$this->indexesByName = array(
'index1' => 'test1',
'index2' => 'test2',
);
/** @var $defaultIndex \PHPUnit_Framework_MockObject_MockObject|\Elastica\Index */
$defaultIndex = $this->getMockBuilder('Elastica\Index')
->disableOriginalConstructor()
->getMock();
$defaultIndex->expects($this->any())
->method('getName')
->will($this->returnValue($this->defaultIndexName));
$this->indexManager = new IndexManager($this->indexesByName, $defaultIndex);
}
public function testGetAllIndexes()
{
$this->assertEquals($this->indexesByName, $this->indexManager->getAllIndexes());
}
public function testGetIndex()
{
$this->assertEquals($this->indexesByName['index1'], $this->indexManager->getIndex('index1'));
$this->assertEquals($this->indexesByName['index2'], $this->indexManager->getIndex('index2'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testGetIndexShouldThrowExceptionForInvalidName()
{
$this->indexManager->getIndex('index3');
}
public function testGetDefaultIndex()
{
$this->assertEquals('test2', $this->indexManager->getIndex());
$this->assertEquals('test2', $this->indexManager->getDefaultIndex());
}
}

View file

@ -0,0 +1,17 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace FOS\ElasticaBundle\Tests\Integration;
class MappingTest {
}

View file

@ -21,6 +21,7 @@ class ModelToElasticaIdentifierTransformer extends ModelToElasticaAutoTransforme
public function transform($object, array $fields) public function transform($object, array $fields)
{ {
$identifier = $this->propertyAccessor->getValue($object, $this->options['identifier']); $identifier = $this->propertyAccessor->getValue($object, $this->options['identifier']);
return new Document($identifier); return new Document($identifier);
} }
} }

View file

@ -24,6 +24,7 @@
"doctrine/orm": "~2.2", "doctrine/orm": "~2.2",
"doctrine/doctrine-bundle": "~1.2@beta", "doctrine/doctrine-bundle": "~1.2@beta",
"doctrine/mongodb-odm": "1.0.*@beta", "doctrine/mongodb-odm": "1.0.*@beta",
"jms/serializer-bundle": "@stable",
"phpunit/phpunit": "~4.1", "phpunit/phpunit": "~4.1",
"propel/propel1": "1.6.*", "propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev", "pagerfanta/pagerfanta": "1.0.*@dev",