From 66d241099984810e26ae809629b23e7dcade116f Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Mon, 16 Jun 2014 15:57:27 +1000 Subject: [PATCH] Move Indexable callback calculations to a new service --- DependencyInjection/Configuration.php | 17 +- DependencyInjection/FOSElasticaExtension.php | 24 ++- Doctrine/Listener.php | 172 +++++------------- Persister/ObjectPersister.php | 21 +++ Provider/Indexable.php | 174 +++++++++++++++++++ Provider/IndexableInterface.php | 25 +++ Resources/config/config.xml | 5 + Resources/config/mongodb.xml | 3 +- Resources/config/orm.xml | 6 +- Tests/Doctrine/AbstractListenerTest.php | 142 +++++++-------- Tests/Functional/IndexableCallbackTest.php | 51 ++++++ Tests/Functional/TypeObj.php | 25 +++ Tests/Functional/app/Basic/config.yml | 2 +- Tests/Functional/app/ORM/bundles.php | 11 ++ Tests/Functional/app/ORM/config.yml | 44 +++++ Tests/Provider/IndexableTest.php | 91 ++++++++++ composer.json | 1 + 17 files changed, 584 insertions(+), 230 deletions(-) create mode 100644 Provider/Indexable.php create mode 100644 Provider/IndexableInterface.php create mode 100644 Tests/Functional/IndexableCallbackTest.php create mode 100644 Tests/Functional/TypeObj.php create mode 100644 Tests/Functional/app/ORM/bundles.php create mode 100644 Tests/Functional/app/ORM/config.yml create mode 100644 Tests/Provider/IndexableTest.php diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index ce1c982..f32f295 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -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') diff --git a/DependencyInjection/FOSElasticaExtension.php b/DependencyInjection/FOSElasticaExtension.php index 1529544..9e50c0b 100644 --- a/DependencyInjection/FOSElasticaExtension.php +++ b/DependencyInjection/FOSElasticaExtension.php @@ -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; diff --git a/Doctrine/Listener.php b/Doctrine/Listener.php index ff9fc60..4a01aa1 100644 --- a/Doctrine/Listener.php +++ b/Doctrine/Listener.php @@ -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 + ); + } } diff --git a/Persister/ObjectPersister.php b/Persister/ObjectPersister.php index c279ec7..2b6a8af 100644 --- a/Persister/ObjectPersister.php +++ b/Persister/ObjectPersister.php @@ -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; diff --git a/Provider/Indexable.php b/Provider/Indexable.php new file mode 100644 index 0000000..b388c58 --- /dev/null +++ b/Provider/Indexable.php @@ -0,0 +1,174 @@ + + * + * 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()); + } +} diff --git a/Provider/IndexableInterface.php b/Provider/IndexableInterface.php new file mode 100644 index 0000000..4871b58 --- /dev/null +++ b/Provider/IndexableInterface.php @@ -0,0 +1,25 @@ + + * + * 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); +} diff --git a/Resources/config/config.xml b/Resources/config/config.xml index 7687250..f4b2606 100644 --- a/Resources/config/config.xml +++ b/Resources/config/config.xml @@ -9,6 +9,7 @@ FOS\ElasticaBundle\DynamicIndex Elastica\Type FOS\ElasticaBundle\IndexManager + FOS\ElasticaBundle\Provider\Indexable FOS\ElasticaBundle\Resetter FOS\ElasticaBundle\Finder\TransformedFinder FOS\ElasticaBundle\Logger\ElasticaLogger @@ -44,6 +45,10 @@ + + + + diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml index 0af7aa1..e575e5d 100644 --- a/Resources/config/mongodb.xml +++ b/Resources/config/mongodb.xml @@ -15,9 +15,10 @@ - + + diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml index 5bd16e5..43d1670 100644 --- a/Resources/config/orm.xml +++ b/Resources/config/orm.xml @@ -15,10 +15,10 @@ - - - + + + diff --git a/Tests/Doctrine/AbstractListenerTest.php b/Tests/Doctrine/AbstractListenerTest.php index ee657f1..de5ba0c 100644 --- a/Tests/Doctrine/AbstractListenerTest.php +++ b/Tests/Doctrine/AbstractListenerTest.php @@ -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() - { - } -} diff --git a/Tests/Functional/IndexableCallbackTest.php b/Tests/Functional/IndexableCallbackTest.php new file mode 100644 index 0000000..89fca1d --- /dev/null +++ b/Tests/Functional/IndexableCallbackTest.php @@ -0,0 +1,51 @@ + + * + * 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'); + } +} diff --git a/Tests/Functional/TypeObj.php b/Tests/Functional/TypeObj.php new file mode 100644 index 0000000..c264e7b --- /dev/null +++ b/Tests/Functional/TypeObj.php @@ -0,0 +1,25 @@ + + * + * 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; + } +} diff --git a/Tests/Functional/app/Basic/config.yml b/Tests/Functional/app/Basic/config.yml index 7025532..09e5aec 100644 --- a/Tests/Functional/app/Basic/config.yml +++ b/Tests/Functional/app/Basic/config.yml @@ -50,4 +50,4 @@ fos_elastica: _parent: type: "parent" property: "parent" - identifier: "id" \ No newline at end of file + identifier: "id" diff --git a/Tests/Functional/app/ORM/bundles.php b/Tests/Functional/app/ORM/bundles.php new file mode 100644 index 0000000..d0b6efb --- /dev/null +++ b/Tests/Functional/app/ORM/bundles.php @@ -0,0 +1,11 @@ + + * + * 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() + { + } +} diff --git a/composer.json b/composer.json index 8783822..d67e329 100644 --- a/composer.json +++ b/composer.json @@ -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",