Merge branch 'master' of github.com:Exercise/FOQElasticaBundle

This commit is contained in:
Richard Miller 2012-01-05 23:03:00 +00:00
commit 96be3159bc
11 changed files with 319 additions and 16 deletions

View file

@ -20,14 +20,7 @@ class Client extends Elastica_Client
public function request($path, $method, $data = array())
{
$start = microtime(true);
//this is ghetto, but i couldnt figure out another way of making our site continue to behave normally even if ES was not running.
//Any improvements on this welcome. Perhaps another parameter that allows you to control if you want to ignore exceptions about ES not running
try {
$response = parent::request($path, $method, $data);
} catch(\Exception $e) {
//again, ghetto, but couldnt figure out how to return a default empty Elastica_Response
return new \Elastica_Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
$response = parent::request($path, $method, $data);
if (null !== $this->logger) {
$time = microtime(true) - $start;

View file

@ -0,0 +1,48 @@
<?php
namespace FOQ\ElasticaBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;
use InvalidArgumentException;
/**
* Registers Transformer implementations into the TransformerCollection.
*
* @author Tim Nagel <tim@nagel.com.au>
*/
class TransformerPass implements CompilerPassInterface
{
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('foq_elastica.elastica_to_model_transformer.collection.prototype')) {
return;
}
$transformers = array();
foreach ($container->findTaggedServiceIds('foq_elastica.elastica_to_model_transformer') as $id => $tags) {
foreach ($tags as $tag) {
if (empty($tag['index']) || empty($tag['type'])) {
throw new InvalidArgumentException('The Transformer must have both a type and an index defined.');
}
$transformers[$tag['index']][$tag['type']]= new Reference($id);
}
}
foreach ($transformers as $index => $indexTransformers) {
if (!$container->hasDefinition(sprintf('foq_elastica.elastica_to_model_transformer.collection.%s', $index))) {
continue;
}
$index = $container->getDefinition(sprintf('foq_elastica.elastica_to_model_transformer.collection.%s', $index));
$index->replaceArgument(0, $indexTransformers);
}
}
}

View file

@ -71,13 +71,14 @@ class Configuration
->performNoDeepMerging()
->children()
->scalarNode('client')->end()
->scalarNode('finder')->end()
->arrayNode('type_prototype')
->children()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return 'propel' === $v['driver'] && isset($v['listener']); })
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return 'propel' === $v['driver'] && isset($v['repository']); })
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()
@ -150,9 +151,9 @@ class Configuration
->children()
->arrayNode('persistence')
->validate()
->ifTrue(function($v) { return 'propel' === $v['driver'] && isset($v['listener']); })
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['listener']); })
->thenInvalid('Propel doesn\'t support listeners')
->ifTrue(function($v) { return 'propel' === $v['driver'] && isset($v['repository']); })
->ifTrue(function($v) { return isset($v['driver']) && 'propel' === $v['driver'] && isset($v['repository']); })
->thenInvalid('Propel doesn\'t support the "repository" parameter')
->end()
->children()

View file

@ -113,6 +113,9 @@ class FOQElasticaExtension extends Extension
'mappings' => array()
)
);
if (isset($index['finder'])) {
$this->loadIndexFinder($container, $name, $indexId);
}
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
@ -122,6 +125,32 @@ class FOQElasticaExtension extends Extension
return $indexIds;
}
/**
* Loads the configured index finders.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* @param string $name The index name
* @param string $indexId The index service identifier
* @return string
*/
protected function loadIndexFinder(ContainerBuilder $container, $name, $indexId)
{
$abstractTransformerId = 'foq_elastica.elastica_to_model_transformer.collection.prototype';
$transformerId = sprintf('foq_elastica.elastica_to_model_transformer.collection.%s', $name);
$transformerDef = new DefinitionDecorator($abstractTransformerId);
$container->setDefinition($transformerId, $transformerDef);
$abstractFinderId = 'foq_elastica.finder.prototype';
$finderId = sprintf('foq_elastica.finder.%s', $name);
$finderDef = new DefinitionDecorator($abstractFinderId);
$finderDef->replaceArgument(0, new Reference($indexId));
$finderDef->replaceArgument(1, new Reference($transformerId));
$container->setDefinition($finderId, $finderDef);
return $finderId;
}
/**
* Loads the configured types.
*
@ -203,6 +232,7 @@ class FOQElasticaExtension extends Extension
$abstractId = sprintf('foq_elastica.elastica_to_model_transformer.prototype.%s', $typeConfig['driver']);
$serviceId = sprintf('foq_elastica.elastica_to_model_transformer.%s.%s', $indexName, $typeName);
$serviceDef = new DefinitionDecorator($abstractId);
$serviceDef->addTag('foq_elastica.elastica_to_model_transformer', array('type' => $typeName, 'index' => $indexName));
// Doctrine has a mandatory service as first argument
$argPos = ('propel' === $typeConfig['driver']) ? 0 : 1;
@ -379,5 +409,4 @@ class FOQElasticaExtension extends Extension
$container->setAlias('foq_elastica.manager', sprintf('foq_elastica.manager.%s', $defaultManagerService));
}
}
}

View file

@ -48,6 +48,16 @@ abstract class AbstractElasticaToModelTransformer implements ElasticaToModelTran
$this->options = array_merge($this->options, $options);
}
/**
* Returns the object class that is used for conversion.
*
* @return string
*/
public function getObjectClass()
{
return $this->objectClass;
}
/**
* Transforms an array of elastica objects into an array of
* model objects fetched from the doctrine repository

View file

@ -5,6 +5,7 @@ namespace FOQ\ElasticaBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\AddProviderPass;
use FOQ\ElasticaBundle\DependencyInjection\Compiler\TransformerPass;
class FOQElasticaBundle extends Bundle
{
@ -13,5 +14,6 @@ class FOQElasticaBundle extends Bundle
parent::build($container);
$container->addCompilerPass(new AddProviderPass());
$container->addCompilerPass(new TransformerPass());
}
}

View file

@ -253,7 +253,7 @@ Its class must implement `FOQ\ElasticaBundle\Provider\ProviderInterface`.
}
}
You will find a more complete implementation example in `src/FOQ/ElasticaBundle/Provider/Doctrine/ORM/Provider.php`.
You will find a more complete implementation example in `src/FOQ/ElasticaBundle/Doctrine/AbstractProvider.php`.
### Search
@ -303,6 +303,25 @@ You can even get paginated results!
/** var Pagerfanta\Pagerfanta */
$userPaginator = $finder->findPaginated('bob');
##### Index wide finder
You can also define a finder that will work on the entire index. Adjust your index
configuration as per below:
foq_elastica:
indexes:
website:
client: default
finder:
You can now use the index wide finder service `foq_elastica.finder.website`:
/** var FOQ\ElasticaBundle\Finder\MappedFinder */
$finder = $container->get('foq_elastica.finder.website');
// Returns a mixed array of any objects mapped
$results = $finder->find('bob');
### Realtime, selective index update
If you use the Doctrine integration, you can let ElasticaBundle update the indexes automatically
@ -361,3 +380,36 @@ Any setting can be specified when declaring a type. For example, to enable a cus
blog:
mappings:
title: { boost: 8, analyzer: my_analyzer }
### Overriding the Client class to suppress exceptions
By default, exceptions from the Elastica client library will propogate through
the bundle's Client class. For instance, if the elasticsearch server is offline,
issuing a request will result in an `Elastica_Exception_Client` being thrown.
Depending on your needs, it may be desirable to suppress these exceptions and
allow searches to fail silently.
One way to achieve this is to override the `foq_elastica.client.class` service
container parameter with a custom class. In the following example, we override
the `Client::request()` method and return the equivalent of an empty search
response if an exception occurred.
```
<?php
namespace Acme\ElasticaBundle;
use FOQ\ElasticaBundle\Client as BaseClient;
class Client extends BaseClient
{
public function request($path, $method, $data = array())
{
try {
return parent::request($path, $method, $data);
} catch (\Elastica_Exception_Abstract $e) {
return new \Elastica_Response('{"took":0,"timed_out":false,"hits":{"total":0,"max_score":0,"hits":[]}}');
}
}
}
```

View file

@ -11,9 +11,9 @@
<parameter key="foq_elastica.logger.class">FOQ\ElasticaBundle\Logger\ElasticaLogger</parameter>
<parameter key="foq_elastica.data_collector.class">FOQ\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
<parameter key="foq_elastica.manager.class">FOQ\ElasticaBundle\Manager\RepositoryManager</parameter>
<parameter key="foq_elastica.elastica_to_model_transformer.collection.class">FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection</parameter>
</parameters>
<services>
<service id="foq_elastica.logger" class="%foq_elastica.logger.class%">
@ -63,6 +63,10 @@
<argument /> <!-- options -->
</service>
<service id="foq_elastica.elastica_to_model_transformer.collection.prototype" class="%foq_elastica.elastica_to_model_transformer.collection.class%" public="true" abstract="true">
<argument type="collection" /> <!-- transformers -->
<argument type="collection" /> <!-- options -->
</service>
</services>
</container>

View file

@ -0,0 +1,92 @@
<?php
namespace FOQ\ElasticaBundle\Tests\Transformer;
use FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection;
class ElasticaToModelTransformerCollectionTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerCollection
*/
protected $collection;
protected $transformers = array();
protected function collectionSetup()
{
$transformer1 = $this->getMock('FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface');
$transformer1->expects($this->any())
->method('getObjectClass')
->will($this->returnValue('FOQ\ElasticaBundle\Tests\Transformer\POPO'));
$transformer2 = $this->getMock('FOQ\ElasticaBundle\Transformer\ElasticaToModelTransformerInterface');
$transformer2->expects($this->any())
->method('getObjectClass')
->will($this->returnValue('FOQ\ElasticaBundle\Tests\Transformer\POPO2'));
$this->collection = new ElasticaToModelTransformerCollection($this->transformers = array(
'type1' => $transformer1,
'type2' => $transformer2,
), array());
}
public function testGetObjectClass()
{
$this->collectionSetup();
$objectClasses = $this->collection->getObjectClass();
$this->assertEquals(array(
'type1' => 'FOQ\ElasticaBundle\Tests\Transformer\POPO',
'type2' => 'FOQ\ElasticaBundle\Tests\Transformer\POPO2'
), $objectClasses);
}
public function testTransformDelegatesToTransformers()
{
$this->collectionSetup();
$document1 = new \Elastica_Document(123, array('data' => 'lots of data'), 'type1');
$document2 = new \Elastica_Document(124, array('data' => 'not so much data'), 'type2');
$result1 = new POPO(123, 'lots of data');
$result2 = new POPO2(124, 'not so much data');
$this->transformers['type1']->expects($this->once())
->method('transform')
->with(array($document1))
->will($this->returnValue(array($result1)));
$this->transformers['type2']->expects($this->once())
->method('transform')
->with(array($document2))
->will($this->returnValue(array($result2)));
$results = $this->collection->transform(array($document1, $document2));
$this->assertEquals(array(
$result1,
$result2,
), $results);
}
}
class POPO
{
public $id;
public $data;
public function __construct($id, $data)
{
$this->data = $data;
$this->id = $id;
}
public function getId()
{
return $this->id;
}
}
class POPO2 extends POPO
{
}

View file

@ -0,0 +1,65 @@
<?php
namespace FOQ\ElasticaBundle\Transformer;
/**
* Holds a collection of transformers for an index wide transformation.
*
* @author Tim Nagel <tim@nagel.com.au>
*/
class ElasticaToModelTransformerCollection implements ElasticaToModelTransformerInterface
{
protected $transformers = array();
protected $options = array(
'identifier' => 'id'
);
public function __construct(array $transformers, array $options)
{
$this->transformers = $transformers;
$this->options = array_merge($this->options, $options);
}
public function getObjectClass()
{
return array_map(function ($transformer) {
return $transformer->getObjectClass();
}, $this->transformers);
}
public function transform(array $elasticaObjects)
{
$sorted = array();
$order = array();
foreach ($elasticaObjects as $object) {
$sorted[$object->getType()][] = $object;
$order[] = sprintf('%s-%s', $object->getType(), $object->getId());
}
$transformed = array();
foreach ($sorted AS $type => $objects) {
$transformed = array_merge($transformed, $this->transformers[$type]->transform($objects));
}
$positions = array_flip($order);
$identifierGetter = 'get' . ucfirst($this->options['identifier']);
$classMap = $this->getTypeToClassMap();
usort($transformed, function($a, $b) use ($positions, $identifierGetter, $classMap)
{
$aType = array_search(get_class($a), $classMap);
$bType = array_search(get_class($b), $classMap);
return $positions["{$aType}-{$a->$identifierGetter()}"] > $positions["{$bType}-{$b->$identifierGetter()}"];
});
return $transformed;
}
protected function getTypeToClassMap()
{
return array_map(function ($transformer) {
return $transformer->getObjectClass();
}, $this->transformers);
}
}

View file

@ -15,4 +15,11 @@ interface ElasticaToModelTransformerInterface
* @return array of model objects
**/
function transform(array $elasticaObjects);
/**
* Returns the object class used by the transformer.
*
* @return string
*/
function getObjectClass();
}