Move Indexable callback calculations to a new service

This commit is contained in:
Tim Nagel 2014-06-16 15:57:27 +10:00
parent 14083496d7
commit 66d2410999
17 changed files with 584 additions and 230 deletions

View file

@ -187,9 +187,23 @@ class Configuration implements ConfigurationInterface
return $v;
})
->end()
->beforeNormalization()
->ifTrue(function ($v) {
return isset($v['persistence']) &&
isset($v['persistence']['listener']) &&
isset($v['persistence']['listener']['is_indexable_callback']);
})
->then(function ($v) {
$v['indexable_callback'] = $v['persistence']['listener']['is_indexable_callback'];
unset($v['persistence']['listener']['is_indexable_callback']);
return $v;
})
->end()
->children()
->scalarNode('index_analyzer')->end()
->scalarNode('search_analyzer')->end()
->scalarNode('indexable_callback')->end()
->append($this->getPersistenceNode())
->append($this->getSerializerNode())
->end()
@ -230,7 +244,7 @@ class Configuration implements ConfigurationInterface
unset($v[$prop]);
}
}
return $v;
})
->end()
@ -674,7 +688,6 @@ class Configuration implements ConfigurationInterface
->treatTrueLike('fos_elastica.logger')
->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
->end()
->arrayNode('finder')

View file

@ -186,8 +186,11 @@ class FOSElasticaExtension extends Extension
*/
protected function loadTypes(array $types, ContainerBuilder $container, $indexName, $indexId, array $typePrototypeConfig)
{
$indexableCallbacks = array();
foreach ($types as $name => $type) {
$type = self::deepArrayUnion($typePrototypeConfig, $type);
$typeName = sprintf('%s/%s', $indexName, $name);
$typeId = sprintf('%s.%s', $indexId, $name);
$typeDefArgs = array($name);
$typeDef = new Definition('%fos_elastica.type.class%', $typeDefArgs);
@ -240,7 +243,6 @@ class FOSElasticaExtension extends Extension
}
if (isset($type['_parent'])) {
$this->indexConfigs[$indexName]['config']['properties'][$name]['_parent'] = array('type' => $type['_parent']['type']);
$typeName = sprintf('%s/%s', $indexName, $name);
$this->typeFields[$typeName]['_parent'] = $type['_parent'];
}
if (isset($type['persistence'])) {
@ -252,6 +254,9 @@ class FOSElasticaExtension extends Extension
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'];
}
@ -271,6 +276,9 @@ class FOSElasticaExtension extends Extension
}
}
}
$indexable = $container->getDefinition('fos_elastica.indexable');
$indexable->replaceArgument(0, $indexableCallbacks);
}
/**
@ -431,8 +439,7 @@ class FOSElasticaExtension extends Extension
$listenerId = sprintf('fos_elastica.listener.%s.%s', $indexName, $typeName);
$listenerDef = new DefinitionDecorator($abstractListenerId);
$listenerDef->replaceArgument(0, new Reference($objectPersisterId));
$listenerDef->replaceArgument(1, $typeConfig['model']);
$listenerDef->replaceArgument(2, $this->getDoctrineEvents($typeConfig));
$listenerDef->replaceArgument(1, $this->getDoctrineEvents($typeConfig));
$listenerDef->replaceArgument(3, $typeConfig['identifier']);
if ($typeConfig['listener']['logger']) {
$listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
@ -442,18 +449,7 @@ class FOSElasticaExtension extends Extension
case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
}
if (isset($typeConfig['listener']['is_indexable_callback'])) {
$callback = $typeConfig['listener']['is_indexable_callback'];
if (is_array($callback)) {
list($class) = $callback + array(null);
if (is_string($class) && !class_exists($class)) {
$callback[0] = new Reference($class);
}
}
$listenerDef->addMethodCall('setIsIndexableCallback', array($callback));
}
$container->setDefinition($listenerId, $listenerDef);
return $listenerId;

View file

@ -2,15 +2,13 @@
namespace FOS\ElasticaBundle\Doctrine;
use Psr\Log\LoggerInterface;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use FOS\ElasticaBundle\Provider\IndexableInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Automatically update ElasticSearch based on changes to the Doctrine source
@ -25,13 +23,6 @@ class Listener implements EventSubscriber
*/
protected $objectPersister;
/**
* Class of the domain model
*
* @var string
*/
protected $objectClass;
/**
* List of subscribed events
*
@ -46,13 +37,6 @@ class Listener implements EventSubscriber
*/
protected $esIdentifierField;
/**
* Callback for determining if an object should be indexed
*
* @var mixed
*/
protected $isIndexableCallback;
/**
* Objects scheduled for insertion and replacement
*/
@ -64,13 +48,6 @@ class Listener implements EventSubscriber
*/
public $scheduledForDeletion = array();
/**
* An instance of ExpressionLanguage
*
* @var ExpressionLanguage
*/
protected $expressionLanguage;
/**
* PropertyAccessor instance
*
@ -78,26 +55,36 @@ class Listener implements EventSubscriber
*/
protected $propertyAccessor;
/**
* @var \FOS\ElasticaBundle\Provider\IndexableInterface
*/
private $indexable;
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
* @param string $objectClass
* @param array $events
* @param string $esIdentifierField
* @param array $events
* @param IndexableInterface $indexable
* @param string $esIdentifierField
* @param null $logger
*/
public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $events, $esIdentifierField = 'id', $logger = null)
{
$this->objectPersister = $objectPersister;
$this->objectClass = $objectClass;
$this->events = $events;
$this->esIdentifierField = $esIdentifierField;
public function __construct(
ObjectPersisterInterface $objectPersister,
array $events,
IndexableInterface $indexable,
$esIdentifierField = 'id',
$logger = null
) {
$this->esIdentifierField = $esIdentifierField;
$this->events = $events;
$this->indexable = $indexable;
$this->objectPersister = $objectPersister;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
if ($logger) {
$this->objectPersister->setLogger($logger);
}
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
@ -108,82 +95,6 @@ class Listener implements EventSubscriber
return $this->events;
}
/**
* Set the callback for determining object index eligibility.
*
* If callback is a string, it must be public method on the object class
* that expects no arguments and returns a boolean. Otherwise, the callback
* should expect the object for consideration as its only argument and
* return a boolean.
*
* @param callback $callback
* @throws \RuntimeException if the callback is not callable
*/
public function setIsIndexableCallback($callback)
{
if (is_string($callback)) {
if (!is_callable(array($this->objectClass, $callback))) {
if (false !== ($expression = $this->getExpressionLanguage())) {
$callback = new Expression($callback);
try {
$expression->compile($callback, array($this->getExpressionVar()));
} catch (SyntaxError $e) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable or a valid expression.', $this->objectClass, $callback), 0, $e);
}
} else {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $this->objectClass, $callback));
}
}
} elseif (!is_callable($callback)) {
if (is_array($callback)) {
list($class, $method) = $callback + array(null, null);
if (is_object($class)) {
$class = get_class($class);
}
if ($class && $method) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $class, $method));
}
}
throw new \RuntimeException('Indexable callback is not callable.');
}
$this->isIndexableCallback = $callback;
}
/**
* Return whether the object is indexable with respect to the callback.
*
* @param object $object
* @return boolean
*/
protected function isObjectIndexable($object)
{
if (!$this->isIndexableCallback) {
return true;
}
if ($this->isIndexableCallback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($this->isIndexableCallback, array($this->getExpressionVar($object) => $object));
}
return is_string($this->isIndexableCallback)
? call_user_func(array($object, $this->isIndexableCallback))
: call_user_func($this->isIndexableCallback, $object);
}
/**
* @param mixed $object
* @return string
*/
private function getExpressionVar($object = null)
{
$class = $object ?: $this->objectClass;
$ref = new \ReflectionClass($class);
return strtolower($ref->getShortName());
}
/**
* Provides unified method for retrieving a doctrine object from an EventArgs instance
*
@ -204,27 +115,11 @@ class Listener implements EventSubscriber
throw new \RuntimeException('Unable to retrieve object from EventArgs.');
}
/**
* @return bool|ExpressionLanguage
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
public function postPersist(EventArgs $eventArgs)
{
$entity = $this->getDoctrineObject($eventArgs);
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) {
$this->scheduledForInsertion[] = $entity;
}
}
@ -233,7 +128,7 @@ class Listener implements EventSubscriber
{
$entity = $this->getDoctrineObject($eventArgs);
if ($entity instanceof $this->objectClass) {
if ($this->objectPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate[] = $entity;
} else {
@ -251,7 +146,7 @@ class Listener implements EventSubscriber
{
$entity = $this->getDoctrineObject($eventArgs);
if ($entity instanceof $this->objectClass) {
if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
}
}
@ -305,4 +200,19 @@ class Listener implements EventSubscriber
$this->scheduledForDeletion[] = $identifierValue;
}
}
/**
* Checks if the object is indexable or not.
*
* @param object $object
* @return bool
*/
private function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
$this->objectPersister->getType()->getIndex()->getName(),
$this->objectPersister->getType()->getName(),
$object
);
}
}

View file

@ -31,6 +31,27 @@ class ObjectPersister implements ObjectPersisterInterface
$this->fields = $fields;
}
/**
* @internal Temporary method that will be removed.
*
* @return Type
*/
public function getType()
{
return $this->type;
}
/**
* If the ObjectPersister handles a given object.
*
* @param object $object
* @return bool
*/
public function handlesObject($object)
{
return $object instanceof $this->objectClass;
}
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;

174
Provider/Indexable.php Normal file
View file

@ -0,0 +1,174 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Provider;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class Indexable implements IndexableInterface
{
/**
* An array of raw configured callbacks for all types.
*
* @var array
*/
private $callbacks = array();
/**
* An instance of ExpressionLanguage
*
* @var ExpressionLanguage
*/
private $expressionLanguage;
/**
* An array of initialised callbacks.
*
* @var array
*/
private $initialisedCallbacks = array();
/**
* PropertyAccessor instance
*
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param array $callbacks
*/
public function __construct(array $callbacks)
{
$this->callbacks = $callbacks;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* Return whether the object is indexable with respect to the callback.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object)
{
$type = sprintf('%s/%s', $indexName, $typeName);
$callback = $this->getCallback($type, $object);
if (!$callback) {
return true;
}
if ($callback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
$this->getExpressionVar($object) => $object
));
}
return is_string($callback)
? call_user_func(array($object, $callback))
: call_user_func($callback, $object);
}
/**
* Builds and initialises a callback.
*
* @param string $type
* @param object $object
* @return mixed
*/
private function buildCallback($type, $object)
{
if (!array_key_exists($type, $this->callbacks)) {
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not configured', $type));
}
$callback = $this->callbacks[$type];
if (is_callable($callback) or is_callable(array($object, $callback))) {
return $callback;
}
if (is_array($callback)) {
list($class, $method) = $callback + array(null, null);
if (is_object($class)) {
$class = get_class($class);
}
if ($class && $method) {
throw new \InvalidArgumentException(sprintf('Callback for type "%s", "%s::%s()", is not callable.', $type, $class, $method));
}
}
if (is_string($callback) && $expression = $this->getExpressionLanguage()) {
$callback = new Expression($callback);
try {
$expression->compile($callback, array('object', $this->getExpressionVar($object)));
return $callback;
} catch (SyntaxError $e) {
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is an invalid expression', $type), $e->getCode(), $e);
}
}
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
}
/**
* Retreives a cached callback, or creates a new callback if one is not found.
*
* @param string $type
* @param object $object
* @return mixed
*/
private function getCallback($type, $object)
{
if (!array_key_exists($type, $this->initialisedCallbacks)) {
$this->initialisedCallbacks[$type] = $this->buildCallback($type, $object);
}
return $this->initialisedCallbacks[$type];
}
/**
* @return bool|ExpressionLanguage
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
/**
* @param mixed $object
* @return string
*/
private function getExpressionVar($object = null)
{
$ref = new \ReflectionClass($object);
return strtolower($ref->getShortName());
}
}

View file

@ -0,0 +1,25 @@
<?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\Provider;
interface IndexableInterface
{
/**
* Checks if an object passed should be indexable or not.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object);
}

View file

@ -9,6 +9,7 @@
<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>
@ -44,6 +45,10 @@
<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 -->

View file

@ -15,9 +15,10 @@
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument type="service" id="fos_elastica.indexable" />
<argument/> <!-- identifier -->
<argument /> <!-- logger -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false">

View file

@ -15,10 +15,10 @@
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->
<argument/> <!-- identifier -->
<argument /> <!-- check method -->
<argument type="service" id="fos_elastica.indexable" />
<argument /> <!-- identifier -->
<argument /> <!-- logger -->
</service>
<service id="fos_elastica.elastica_to_model_transformer.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\ElasticaToModelTransformer" public="false">

View file

@ -11,12 +11,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
public function testObjectInsertedOnPersist()
{
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
$listener = $this->createListener($persister, get_class($entity), array());
$listener = $this->createListener($persister, array(), $indexable);
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
@ -28,18 +28,14 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$listener->postFlush($eventArgs);
}
/**
* @dataProvider provideIsIndexableCallbacks
*/
public function testNonIndexableObjectNotInsertedOnPersist($isIndexableCallback)
public function testNonIndexableObjectNotInsertedOnPersist()
{
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1, false);
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, false);
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener = $this->createListener($persister, array(), $indexable);
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
@ -54,12 +50,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
public function testObjectReplacedOnUpdate()
{
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
$listener = $this->createListener($persister, get_class($entity), array());
$listener = $this->createListener($persister, array(), $indexable);
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
@ -73,17 +69,15 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$listener->postFlush($eventArgs);
}
/**
* @dataProvider provideIsIndexableCallbacks
*/
public function testNonIndexableObjectRemovedOnUpdate($isIndexableCallback)
public function testNonIndexableObjectRemovedOnUpdate()
{
$classMetadata = $this->getMockClassMetadata();
$objectManager = $this->getMockObjectManager();
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1, false);
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $objectManager);
$indexable = $this->getMockIndexable('index', 'type', $entity, false);
$objectManager->expects($this->any())
->method('getClassMetadata')
@ -95,8 +89,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener = $this->createListener($persister, array(), $indexable);
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
@ -115,10 +108,11 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
$classMetadata = $this->getMockClassMetadata();
$objectManager = $this->getMockObjectManager();
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $objectManager);
$indexable = $this->getMockIndexable('index', 'type', $entity);
$objectManager->expects($this->any())
->method('getClassMetadata')
@ -130,7 +124,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, get_class($entity), array());
$listener = $this->createListener($persister, array(), $indexable);
$listener->preRemove($eventArgs);
$this->assertEquals($entity->getId(), current($listener->scheduledForDeletion));
@ -146,11 +140,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
$classMetadata = $this->getMockClassMetadata();
$objectManager = $this->getMockObjectManager();
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1);
$entity->identifier = 'foo';
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $objectManager);
$indexable = $this->getMockIndexable('index', 'type', $entity);
$objectManager->expects($this->any())
->method('getClassMetadata')
@ -162,7 +157,7 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
$listener = $this->createListener($persister, get_class($entity), array(), 'identifier');
$listener = $this->createListener($persister, array(), $indexable, 'identifier');
$listener->preRemove($eventArgs);
$this->assertEquals($entity->identifier, current($listener->scheduledForDeletion));
@ -174,36 +169,6 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
$listener->postFlush($eventArgs);
}
/**
* @dataProvider provideInvalidIsIndexableCallbacks
* @expectedException \RuntimeException
*/
public function testInvalidIsIndexableCallbacks($isIndexableCallback)
{
$listener = $this->createListener($this->getMockPersister(), 'FOS\ElasticaBundle\Tests\Doctrine\Listener\Entity', array());
$listener->setIsIndexableCallback($isIndexableCallback);
}
public function provideInvalidIsIndexableCallbacks()
{
return array(
array('nonexistentEntityMethod'),
array(array(new Listener\IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
);
}
public function provideIsIndexableCallbacks()
{
return array(
array('getIsIndexable'),
array(array(new Listener\IndexableDecider(), 'isIndexable')),
array(function(Listener\Entity $entity) { return $entity->getIsIndexable(); }),
array('entity.getIsIndexable()')
);
}
abstract protected function getLifecycleEventArgsClass();
abstract protected function getListenerClass();
@ -240,9 +205,48 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
->getMock();
}
private function getMockPersister()
private function getMockPersister($object, $indexName, $typeName)
{
return $this->getMock('FOS\ElasticaBundle\Persister\ObjectPersisterInterface');
$mock = $this->getMockBuilder('FOS\ElasticaBundle\Persister\ObjectPersister')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->any())
->method('handlesObject')
->with($object)
->will($this->returnValue(true));
$index = $this->getMockBuilder('Elastica\Index')->disableOriginalConstructor()->getMock();
$index->expects($this->any())
->method('getName')
->will($this->returnValue($indexName));
$type = $this->getMockBuilder('Elastica\Type')->disableOriginalConstructor()->getMock();
$type->expects($this->any())
->method('getName')
->will($this->returnValue($typeName));
$type->expects($this->any())
->method('getIndex')
->will($this->returnValue($index));
$mock->expects($this->any())
->method('getType')
->will($this->returnValue($type));
return $mock;
}
private function getMockIndexable($indexName, $typeName, $object, $return = null)
{
$mock = $this->getMock('FOS\ElasticaBundle\Provider\IndexableInterface');
if (null !== $return) {
$mock->expects($this->once())
->method('isObjectIndexable')
->with($indexName, $typeName, $object)
->will($this->returnValue($return));
}
return $mock;
}
}
@ -251,33 +255,15 @@ namespace FOS\ElasticaBundle\Tests\Doctrine\Listener;
class Entity
{
private $id;
private $isIndexable;
public function __construct($id, $isIndexable = true)
public function __construct($id)
{
$this->id = $id;
$this->isIndexable = $isIndexable;
}
public function getId()
{
return $this->id;
}
public function getIsIndexable()
{
return $this->isIndexable;
}
}
class IndexableDecider
{
public function isIndexable(Entity $entity)
{
return $entity->getIsIndexable();
}
protected function internalMethod()
{
}
}

View file

@ -0,0 +1,51 @@
<?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;
/**
* @group functional
*/
class IndexableCallbackTest extends WebTestCase
{
/**
* 2 reasons for this test:
*
* 1) To test that the configuration rename from is_indexable_callback under the listener
* key is respected, and
* 2) To test the Extension's set up of the Indexable service.
*/
public function testIndexableCallback()
{
$client = $this->createClient(array('test_case' => 'ORM'));
/** @var \FOS\ElasticaBundle\Provider\Indexable $in */
$in = $client->getContainer()->get('fos_elastica.indexable');
$this->assertTrue($in->isObjectIndexable('index', 'type', new TypeObj()));
$this->assertFalse($in->isObjectIndexable('index', 'type2', new TypeObj()));
$this->assertFalse($in->isObjectIndexable('index', 'type3', new TypeObj()));
}
protected function setUp()
{
parent::setUp();
$this->deleteTmpDir('ORM');
}
protected function tearDown()
{
parent::tearDown();
$this->deleteTmpDir('ORM');
}
}

View file

@ -0,0 +1,25 @@
<?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\Tests\Functional;
class TypeObj
{
public function isIndexable()
{
return true;
}
public function isntIndexable()
{
return false;
}
}

View file

@ -50,4 +50,4 @@ fos_elastica:
_parent:
type: "parent"
property: "parent"
identifier: "id"
identifier: "id"

View file

@ -0,0 +1,11 @@
<?php
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use FOS\ElasticaBundle\FOSElasticaBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
return array(
new FrameworkBundle(),
new FOSElasticaBundle(),
new DoctrineBundle()
);

View file

@ -0,0 +1,44 @@
imports:
- { resource: ./../config/config.yml }
doctrine:
dbal:
path: %kernel.cache_dir%/db.sqlite
charset: UTF8
orm:
auto_generate_proxy_classes: false
auto_mapping: false
fos_elastica:
clients:
default:
url: http://localhost:9200
indexes:
index:
index_name: foselastica_test_%kernel.environment%
types:
type:
properties:
field1: ~
persistence:
driver: orm
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener:
is_indexable_callback: 'object.isIndexable() && !object.isntIndexable()'
type2:
properties:
field1: ~
persistence:
driver: orm
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener:
is_indexable_callback: 'object.isntIndexable()'
type3:
properties:
field1: ~
persistence:
driver: orm
model: FOS\ElasticaBundle\Tests\Functional\TypeObj
listener:
is_indexable_callback: 'isntIndexable'

View file

@ -0,0 +1,91 @@
<?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\Tests\Provider;
use FOS\ElasticaBundle\Provider\Indexable;
class IndexableTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideIsIndexableCallbacks
*/
public function testValidIndexableCallbacks($callback, $return)
{
$indexable = new Indexable(array(
'index/type' => $callback
));
$index = $indexable->isObjectIndexable('index', 'type', new Entity);
$this->assertEquals($return, $index);
}
/**
* @dataProvider provideInvalidIsIndexableCallbacks
* @expectedException \InvalidArgumentException
*/
public function testInvalidIsIndexableCallbacks($callback)
{
$indexable = new Indexable(array(
'index/type' => $callback
));
$indexable->isObjectIndexable('index', 'type', new Entity);
}
public function provideInvalidIsIndexableCallbacks()
{
return array(
array('nonexistentEntityMethod'),
array(array(new IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
);
}
public function provideIsIndexableCallbacks()
{
return array(
array('isIndexable', false),
array(array(new IndexableDecider(), 'isIndexable'), true),
array(function(Entity $entity) { return $entity->maybeIndex(); }, true),
array('entity.maybeIndex()', true),
array('!object.isIndexable() && entity.property == "abc"', true),
array('entity.property != "abc"', false),
);
}
}
class Entity
{
public $property = 'abc';
public function isIndexable()
{
return false;
}
public function maybeIndex()
{
return true;
}
}
class IndexableDecider
{
public function isIndexable(Entity $entity)
{
return !$entity->isIndexable();
}
protected function internalMethod()
{
}
}

View file

@ -21,6 +21,7 @@
},
"require-dev":{
"doctrine/orm": "~2.2",
"doctrine/doctrine-bundle": "~1.2@beta",
"doctrine/mongodb-odm": "1.0.*@dev",
"propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev",