Merge branch 'master' of github.com:Exercise/FOQElasticaBundle
This commit is contained in:
commit
96be3159bc
|
@ -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;
|
||||
|
|
48
DependencyInjection/Compiler/TransformerPass.php
Normal file
48
DependencyInjection/Compiler/TransformerPass.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
54
README.md
54
README.md
|
@ -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":[]}}');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
65
Transformer/ElasticaToModelTransformerCollection.php
Normal file
65
Transformer/ElasticaToModelTransformerCollection.php
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue