diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md
index 62e347a..28f04e3 100644
--- a/CHANGELOG-3.0.md
+++ b/CHANGELOG-3.0.md
@@ -14,6 +14,13 @@ To generate a changelog summary since the last version, run
* 3.0.0-ALPHA6
+ * Moved `is_indexable_callback` from the listener properties to a type property called
+ `indexable_callback` which is run when both populating and listening for object
+ changes.
+ * BC BREAK `ObjectPersisterInterface` added method getType() (To be removed during
+ ObjectPersister refactoring before 3.0.0 stable when ObjectPersister will change)
+ * AbstractProvider constructor change: Second argument is now an `IndexableInterface`
+ instance.
* Annotation @Search moved to FOS\ElasticaBundle\Annotation\Search with FOS\ElasticaBundle\Configuration\Search deprecated
* Deprecated FOS\ElasticaBundle\Client in favour of FOS\ElasticaBundle\Elastica\Client
* Deprecated FOS\ElasticaBundle\DynamicIndex in favour of FOS\ElasticaBundle\Elastica\Index
diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 7e8bca4..3564fd4 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -189,9 +189,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()
@@ -458,7 +472,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 7699fd7..d122218 100644
--- a/DependencyInjection/FOSElasticaExtension.php
+++ b/DependencyInjection/FOSElasticaExtension.php
@@ -194,6 +194,8 @@ class FOSElasticaExtension extends Extension
*/
private function loadTypes(array $types, ContainerBuilder $container, array $indexConfig)
{
+ $indexableCallbacks = array();
+
foreach ($types as $name => $type) {
$indexName = $indexConfig['name'];
@@ -231,6 +233,10 @@ class FOSElasticaExtension extends Extension
}
}
+ if (isset($type['indexable_callback'])) {
+ $indexableCallbacks[sprintf('%s/%s', $indexName, $name)] = $type['indexable_callback'];
+ }
+
$container->setDefinition($typeId, $typeDef);
/*if ($this->serializerConfig) {
@@ -255,6 +261,9 @@ class FOSElasticaExtension extends Extension
$typeDef->addMethodCall('setSerializer', array(array(new Reference($callbackId), 'serialize')));
}*/
}
+
+ $indexable = $container->getDefinition('fos_elastica.indexable');
+ $indexable->replaceArgument(0, $indexableCallbacks);
}
/**
@@ -411,10 +420,12 @@ class FOSElasticaExtension extends Extension
$providerDef = new DefinitionDecorator('fos_elastica.provider.prototype.' . $typeConfig['driver']);
$providerDef->addTag('fos_elastica.provider', array('index' => $indexName, 'type' => $typeName));
$providerDef->replaceArgument(0, new Reference($objectPersisterId));
- $providerDef->replaceArgument(1, $typeConfig['model']);
+ $providerDef->replaceArgument(2, $typeConfig['model']);
// Propel provider can simply ignore Doctrine-specific options
- $providerDef->replaceArgument(2, array_diff_key($typeConfig['provider'], array('service' => 1)));
-
+ $providerDef->replaceArgument(3, array_merge(array_diff_key($typeConfig['provider'], array('service' => 1)), array(
+ 'indexName' => $indexName,
+ 'typeName' => $typeName,
+ )));
$container->setDefinition($providerId, $providerDef);
return $providerId;
@@ -443,25 +454,21 @@ 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(3, $typeConfig['identifier']);
+ $listenerDef->replaceArgument(1, $this->getDoctrineEvents($typeConfig));
+ $listenerDef->replaceArgument(3, array(
+ 'identifier' => $typeConfig['identifier'],
+ 'indexName' => $indexName,
+ 'typeName' => $typeName,
+ ));
if ($typeConfig['listener']['logger']) {
$listenerDef->replaceArgument(4, new Reference($typeConfig['listener']['logger']));
}
- 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));
+ switch ($typeConfig['driver']) {
+ case 'orm': $listenerDef->addTag('doctrine.event_subscriber'); break;
+ case 'mongodb': $listenerDef->addTag('doctrine_mongodb.odm.event_subscriber'); break;
}
+
$container->setDefinition($listenerId, $listenerDef);
return $listenerId;
diff --git a/Doctrine/AbstractProvider.php b/Doctrine/AbstractProvider.php
index b9ffda5..0c43b2d 100644
--- a/Doctrine/AbstractProvider.php
+++ b/Doctrine/AbstractProvider.php
@@ -6,6 +6,7 @@ use Doctrine\Common\Persistence\ManagerRegistry;
use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\AbstractProvider as BaseAbstractProvider;
+use FOS\ElasticaBundle\Provider\IndexableInterface;
abstract class AbstractProvider extends BaseAbstractProvider
{
@@ -15,13 +16,19 @@ abstract class AbstractProvider extends BaseAbstractProvider
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param string $objectClass
- * @param array $options
- * @param ManagerRegistry $managerRegistry
+ * @param IndexableInterface $indexable
+ * @param string $objectClass
+ * @param array $options
+ * @param ManagerRegistry $managerRegistry
*/
- public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options, $managerRegistry)
- {
- parent::__construct($objectPersister, $objectClass, array_merge(array(
+ public function __construct(
+ ObjectPersisterInterface $objectPersister,
+ IndexableInterface $indexable,
+ $objectClass,
+ array $options,
+ ManagerRegistry $managerRegistry
+ ) {
+ parent::__construct($objectPersister, $indexable, $objectClass, array_merge(array(
'clear_object_manager' => true,
'debug_logging' => false,
'ignore_errors' => false,
@@ -53,6 +60,10 @@ abstract class AbstractProvider extends BaseAbstractProvider
$stepStartTime = microtime(true);
}
$objects = $this->fetchSlice($queryBuilder, $batchSize, $offset);
+ if ($loggerClosure) {
+ $stepNbObjects = count($objects);
+ }
+ $objects = array_filter($objects, array($this, 'isObjectIndexable'));
if (!$ignoreErrors) {
$this->objectPersister->insertMany($objects);
@@ -73,7 +84,6 @@ abstract class AbstractProvider extends BaseAbstractProvider
usleep($sleep);
if ($loggerClosure) {
- $stepNbObjects = count($objects);
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$timeDifference = microtime(true) - $stepStartTime;
diff --git a/Doctrine/Listener.php b/Doctrine/Listener.php
index ff9fc60..73a271d 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
*
@@ -40,18 +31,11 @@ class Listener implements EventSubscriber
protected $events;
/**
- * Name of domain model field used as the ES identifier
+ * Configuration for the listener
*
* @var string
*/
- protected $esIdentifierField;
-
- /**
- * Callback for determining if an object should be indexed
- *
- * @var mixed
- */
- protected $isIndexableCallback;
+ private $config;
/**
* 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,38 @@ 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 array $config
+ * @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,
+ array $config = array(),
+ $logger = null
+ ) {
+ $this->config = array_merge(array(
+ 'identifier' => 'id',
+ ), $config);
+ $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 +97,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 +117,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 +130,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 +148,7 @@ class Listener implements EventSubscriber
{
$entity = $this->getDoctrineObject($eventArgs);
- if ($entity instanceof $this->objectClass) {
+ if ($this->objectPersister->handlesObject($entity)) {
$this->scheduleForDeletion($entity);
}
}
@@ -301,8 +198,23 @@ class Listener implements EventSubscriber
*/
protected function scheduleForDeletion($object)
{
- if ($identifierValue = $this->propertyAccessor->getValue($object, $this->esIdentifierField)) {
+ if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
$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->config['indexName'],
+ $this->config['typeName'],
+ $object
+ );
+ }
}
diff --git a/Elastica/Client.php b/Elastica/Client.php
index 64a7d3d..0db15b3 100644
--- a/Elastica/Client.php
+++ b/Elastica/Client.php
@@ -31,7 +31,7 @@ class Client extends BaseClient
'host' => $connection->getHost(),
'port' => $connection->getPort(),
'transport' => $connection->getTransport(),
- 'headers' => $connection->getConfig('headers'),
+ 'headers' => $connection->hasConfig('headers') ? $connection->getConfig('headers') : array(),
);
$this->_logger->logQuery($path, $method, $data, $time, $connection_array, $query);
diff --git a/Elastica/Index.php b/Elastica/Index.php
index 35d49f9..ec32c62 100644
--- a/Elastica/Index.php
+++ b/Elastica/Index.php
@@ -11,6 +11,19 @@ use Elastica\Index as BaseIndex;
*/
class Index extends BaseIndex
{
+ private $originalName;
+
+ /**
+ * Returns the original name of the index if the index has been renamed for reindexing
+ * or realiasing purposes.
+ *
+ * @return string
+ */
+ public function getOriginalName()
+ {
+ return $this->originalName ?: $this->_name;
+ }
+
/**
* Reassign index name
*
@@ -23,6 +36,7 @@ class Index extends BaseIndex
*/
public function overrideName($name)
{
+ $this->originalName = $this->_name;
$this->_name = $name;
}
}
diff --git a/Persister/ObjectPersister.php b/Persister/ObjectPersister.php
index c279ec7..54d5fd1 100644
--- a/Persister/ObjectPersister.php
+++ b/Persister/ObjectPersister.php
@@ -31,6 +31,17 @@ class ObjectPersister implements ObjectPersisterInterface
$this->fields = $fields;
}
+ /**
+ * 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/Propel/Provider.php b/Propel/Provider.php
index 393beba..c242d47 100644
--- a/Propel/Provider.php
+++ b/Propel/Provider.php
@@ -30,14 +30,18 @@ class Provider extends AbstractProvider
$objects = $queryClass::create()
->limit($batchSize)
->offset($offset)
- ->find();
+ ->find()
+ ->getArrayCopy();
+ if ($loggerClosure) {
+ $stepNbObjects = count($objects);
+ }
+ $objects = array_filter($objects, array($this, 'isObjectIndexable'));
- $this->objectPersister->insertMany($objects->getArrayCopy());
+ $this->objectPersister->insertMany($objects);
usleep($sleep);
if ($loggerClosure) {
- $stepNbObjects = count($objects);
$stepCount = $stepNbObjects + $offset;
$percentComplete = 100 * $stepCount / $nbObjects;
$objectsPerSecond = $stepNbObjects / (microtime(true) - $stepStartTime);
diff --git a/Provider/AbstractProvider.php b/Provider/AbstractProvider.php
index 2761a25..82ea914 100644
--- a/Provider/AbstractProvider.php
+++ b/Provider/AbstractProvider.php
@@ -24,23 +24,49 @@ abstract class AbstractProvider implements ProviderInterface
*/
protected $options;
+ /**
+ * @var Indexable
+ */
+ private $indexable;
+
/**
* Constructor.
*
* @param ObjectPersisterInterface $objectPersister
- * @param string $objectClass
- * @param array $options
+ * @param IndexableInterface $indexable
+ * @param string $objectClass
+ * @param array $options
*/
- public function __construct(ObjectPersisterInterface $objectPersister, $objectClass, array $options = array())
- {
- $this->objectPersister = $objectPersister;
+ public function __construct(
+ ObjectPersisterInterface $objectPersister,
+ IndexableInterface $indexable,
+ $objectClass,
+ array $options = array()
+ ) {
+ $this->indexable = $indexable;
$this->objectClass = $objectClass;
+ $this->objectPersister = $objectPersister;
$this->options = array_merge(array(
'batch_size' => 100,
), $options);
}
+ /**
+ * Checks if a given object should be indexed or not.
+ *
+ * @param object $object
+ * @return bool
+ */
+ protected function isObjectIndexable($object)
+ {
+ return $this->indexable->isObjectIndexable(
+ $this->options['indexName'],
+ $this->options['typeName'],
+ $object
+ );
+ }
+
/**
* Get string with RAM usage information (current and peak)
*
diff --git a/Provider/Indexable.php b/Provider/Indexable.php
new file mode 100644
index 0000000..09168a1
--- /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)) {
+ return null;
+ }
+
+ $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/README.md b/README.md
index 7909d2b..631951a 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Symfony2. Features include:
> **Note** Propel support is limited and contributions fixing issues are welcome!
-[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle)
+[![Build Status](https://secure.travis-ci.org/FriendsOfSymfony/FOSElasticaBundle.png?branch=master)](http://travis-ci.org/FriendsOfSymfony/FOSElasticaBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/elastica-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/elastica-bundle) [![Latest Unstable Version](https://poser.pugx.org/friendsofsymfony/elastica-bundle/v/unstable.svg)](https://packagist.org/packages/friendsofsymfony/elastica-bundle)
Documentation
-------------
diff --git a/Resources/config/index.xml b/Resources/config/index.xml
index 85f5744..de1a52e 100644
--- a/Resources/config/index.xml
+++ b/Resources/config/index.xml
@@ -7,12 +7,17 @@
FOS\ElasticaBundle\Elastica\Index
Elastica\Type
+ FOS\ElasticaBundle\Provider\Indexable
FOS\ElasticaBundle\Index\IndexManager
FOS\ElasticaBundle\Index\Resetter
FOS\ElasticaBundle\Finder\TransformedFinder
+
+
+
+
diff --git a/Resources/config/mongodb.xml b/Resources/config/mongodb.xml
index 0c6b2af..ed4787a 100644
--- a/Resources/config/mongodb.xml
+++ b/Resources/config/mongodb.xml
@@ -7,17 +7,18 @@
+
-
-
+
-
-
+
+
+
diff --git a/Resources/config/orm.xml b/Resources/config/orm.xml
index bf67688..bbc1ec4 100644
--- a/Resources/config/orm.xml
+++ b/Resources/config/orm.xml
@@ -7,17 +7,18 @@
+
-
-
+
-
-
+
+
+
diff --git a/Resources/config/propel.xml b/Resources/config/propel.xml
index 4ccc867..102935c 100644
--- a/Resources/config/propel.xml
+++ b/Resources/config/propel.xml
@@ -6,6 +6,7 @@
+
diff --git a/Resources/doc/setup.md b/Resources/doc/setup.md
index 9a39b95..485f290 100644
--- a/Resources/doc/setup.md
+++ b/Resources/doc/setup.md
@@ -7,7 +7,7 @@ A) Install FOSElasticaBundle
FOSElasticaBundle is installed using [Composer](https://getcomposer.org).
```bash
-$ php composer.phar require friendsofsymfony/elastica-bundle "3.0.*"
+$ php composer.phar require friendsofsymfony/elastica-bundle "3.0.*@alpha"
```
### Elasticsearch
diff --git a/Resources/doc/types.md b/Resources/doc/types.md
index aacb09e..be687d7 100644
--- a/Resources/doc/types.md
+++ b/Resources/doc/types.md
@@ -149,6 +149,44 @@ analyzer, you could write:
title: { boost: 8, analyzer: my_analyzer }
```
+Testing if an object should be indexed
+--------------------------------------
+
+FOSElasticaBundle can be configured to automatically index changes made for
+different kinds of objects if your persistence backend supports these methods,
+but in some cases you might want to run an external service or call a property
+on the object to see if it should be indexed.
+
+A property, `indexable_callback` is provided under the type configuration that
+lets you configure this behaviour which will apply for any automated watching
+for changes and for a repopulation of an index.
+
+In the example below, we're checking the enabled property on the user to only
+index enabled users.
+
+```yaml
+ types:
+ users:
+ indexable_callback: 'enabled'
+```
+
+The callback option supports multiple approaches:
+
+* A method on the object itself provided as a string. `enabled` will call
+ `Object->enabled()`
+* An array of a service id and a method which will be called with the object as the first
+ and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
+ method on a service defined as my_custom_service.
+* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
+ expression provided as a string. The object being indexed will be supplied as `object`
+ in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more
+ information on the ExpressionLanguage component and its capabilities see its
+ [documentation](http://symfony.com/doc/current/components/expression_language/index.html)
+
+In all cases, the callback should return a true or false, with true indicating it will be
+indexed, and a false indicating the object should not be indexed, or should be removed
+from the index if we are running an update.
+
Provider Configuration
----------------------
@@ -234,49 +272,6 @@ You can also choose to only listen for some of the events:
> **Propel** doesn't support this feature yet.
-### Checking an entity method for listener
-
-If you use listeners to update your index, you may need to validate your
-entities before you index them (e.g. only index "public" entities). Typically,
-you'll want the listener to be consistent with the provider's query criteria.
-This may be achieved by using the `is_indexable_callback` config parameter:
-
-```yaml
- persistence:
- listener:
- is_indexable_callback: "isPublic"
-```
-
-If `is_indexable_callback` is a string and the entity has a method with the
-specified name, the listener will only index entities for which the method
-returns `true`. Additionally, you may provide a service and method name pair:
-
-```yaml
- persistence:
- listener:
- is_indexable_callback: [ "%custom_service_id%", "isIndexable" ]
-```
-
-In this case, the callback_class will be the `isIndexable()` method on the specified
-service and the object being considered for indexing will be passed as the only
-argument. This allows you to do more complex validation (e.g. ACL checks).
-
-If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language)
-component installed, you can use expressions to evaluate the callback:
-
-```yaml
- persistence:
- listener:
- is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')"
-```
-
-As you might expect, new entities will only be indexed if the callback_class returns
-`true`. Additionally, modified entities will be updated or removed from the
-index depending on whether the callback_class returns `true` or `false`, respectively.
-The delete listener disregards the callback_class.
-
-> **Propel** doesn't support this feature yet.
-
Flushing Method
---------------
diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php
index 1da155a..b474117 100644
--- a/Tests/DependencyInjection/ConfigurationTest.php
+++ b/Tests/DependencyInjection/ConfigurationTest.php
@@ -198,4 +198,50 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
$this->assertCount(3, $configuration['indexes']['test']['types']['test']['properties']);
}
+
+ public function testNestedProperties()
+ {
+ $this->getConfigs(array(
+ 'clients' => array(
+ 'default' => array('url' => 'http://localhost:9200'),
+ ),
+ 'indexes' => array(
+ 'test' => array(
+ 'types' => array(
+ 'user' => array(
+ 'properties' => array(
+ 'field1' => array(),
+ ),
+ 'persistence' => array(),
+ ),
+ 'user_profile' => array(
+ '_parent' => array(
+ 'type' => 'user',
+ 'property' => 'owner',
+ ),
+ 'properties' => array(
+ 'field1' => array(),
+ 'field2' => array(
+ 'type' => 'nested',
+ 'properties' => array(
+ 'nested_field1' => array(
+ 'type' => 'integer'
+ ),
+ 'nested_field2' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'type' => 'integer'
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ ));
+ }
}
diff --git a/Tests/Doctrine/AbstractListenerTest.php b/Tests/Doctrine/AbstractListenerTest.php
index ee657f1..1f238d6 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, array('indexName' => 'index', 'typeName' => 'type'));
$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, array('indexName' => 'index', 'typeName' => 'type'));
$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, array('indexName' => 'index', 'typeName' => 'type'));
$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, array('indexName' => 'index', 'typeName' => 'type'));
$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, array('indexName' => 'index', 'typeName' => 'type'));
$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, array('identifier' => 'identifier', 'indexName' => 'index', 'typeName' => 'type'));
$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/Doctrine/AbstractProviderTest.php b/Tests/Doctrine/AbstractProviderTest.php
index dcceccf..cd0a58c 100644
--- a/Tests/Doctrine/AbstractProviderTest.php
+++ b/Tests/Doctrine/AbstractProviderTest.php
@@ -2,6 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Doctrine;
+use Elastica\Bulk\ResponseSet;
+use Elastica\Response;
+
class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
private $objectClass;
@@ -9,24 +12,26 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
private $objectPersister;
private $options;
private $managerRegistry;
+ private $indexable;
public function setUp()
{
- if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
- $this->markTestSkipped('Doctrine Common is not available.');
- }
+ if (!interface_exists('Doctrine\Common\Persistence\ManagerRegistry')) {
+ $this->markTestSkipped('Doctrine Common is not available.');
+ }
- $this->objectClass = 'objectClass';
- $this->options = array('debug_logging' => true);
+ $this->objectClass = 'objectClass';
+ $this->options = array('debug_logging' => true, 'indexName' => 'index', 'typeName' => 'type');
- $this->objectPersister = $this->getMockObjectPersister();
- $this->managerRegistry = $this->getMockManagerRegistry();
- $this->objectManager = $this->getMockObjectManager();
+ $this->objectPersister = $this->getMockObjectPersister();
+ $this->managerRegistry = $this->getMockManagerRegistry();
+ $this->objectManager = $this->getMockObjectManager();
+ $this->indexable = $this->getMockIndexable();
- $this->managerRegistry->expects($this->any())
- ->method('getManagerForClass')
- ->with($this->objectClass)
- ->will($this->returnValue($this->objectManager));
+ $this->managerRegistry->expects($this->any())
+ ->method('getManagerForClass')
+ ->with($this->objectClass)
+ ->will($this->returnValue($this->objectManager));
}
/**
@@ -59,14 +64,13 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
->with($queryBuilder, $batchSize, $offset)
->will($this->returnValue($objects));
- $this->objectPersister->expects($this->at($i))
- ->method('insertMany')
- ->with($objects);
-
$this->objectManager->expects($this->at($i))
->method('clear');
}
+ $this->objectPersister->expects($this->exactly(count($objectsByIteration)))
+ ->method('insertMany');
+
$provider->populate();
}
@@ -159,6 +163,36 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
$provider->populate(null, array('ignore-errors' => false));
}
+ public function testPopulateRunsIndexCallable()
+ {
+ $nbObjects = 2;
+ $objects = array(1, 2);
+
+ $provider = $this->getMockAbstractProvider();
+ $provider->expects($this->any())
+ ->method('countObjects')
+ ->will($this->returnValue($nbObjects));
+ $provider->expects($this->any())
+ ->method('fetchSlice')
+ ->will($this->returnValue($objects));
+
+ $this->indexable->expects($this->at(0))
+ ->method('isObjectIndexable')
+ ->with('index', 'type', 1)
+ ->will($this->returnValue(false));
+ $this->indexable->expects($this->at(1))
+ ->method('isObjectIndexable')
+ ->with('index', 'type', 2)
+ ->will($this->returnValue(true));
+
+
+ $this->objectPersister->expects($this->once())
+ ->method('insertMany')
+ ->with(array(1 => 2));
+
+ $provider->populate();
+ }
+
/**
* @return \FOS\ElasticaBundle\Doctrine\AbstractProvider|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -166,6 +200,7 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
return $this->getMockForAbstractClass('FOS\ElasticaBundle\Doctrine\AbstractProvider', array(
$this->objectPersister,
+ $this->indexable,
$this->objectClass,
$this->options,
$this->managerRegistry,
@@ -177,9 +212,9 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
*/
private function getMockBulkResponseException()
{
- return $this->getMockBuilder('Elastica\Exception\Bulk\ResponseException')
- ->disableOriginalConstructor()
- ->getMock();
+ return $this->getMock('Elastica\Exception\Bulk\ResponseException', null, array(
+ new ResponseSet(new Response(array()), array())
+ ));
}
/**
@@ -205,6 +240,14 @@ class AbstractProviderTest extends \PHPUnit_Framework_TestCase
{
return $this->getMock('FOS\ElasticaBundle\Persister\ObjectPersisterInterface');
}
+
+ /**
+ * @return \FOS\ElasticaBundle\Provider\IndexableInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockIndexable()
+ {
+ return $this->getMock('FOS\ElasticaBundle\Provider\IndexableInterface');
+ }
}
/**
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/MappingToElasticaTest.php b/Tests/Functional/MappingToElasticaTest.php
index 3c32e10..bffcf76 100644
--- a/Tests/Functional/MappingToElasticaTest.php
+++ b/Tests/Functional/MappingToElasticaTest.php
@@ -18,13 +18,28 @@ use Symfony\Bundle\FrameworkBundle\Client;
*/
class MappingToElasticaTest extends WebTestCase
{
- public function testReset()
+ public function testResetIndexAddsMappings()
{
$client = $this->createClient(array('test_case' => 'Basic'));
$resetter = $this->getResetter($client);
-
$resetter->resetIndex('index');
+
+ $type = $this->getType($client);
+ $mapping = $type->getMapping();
+
+ $this->assertNotEmpty($mapping, 'Mapping was populated');
+ }
+
+ public function testResetType()
+ {
+ $client = $this->createClient(array('test_case' => 'Basic'));
+ $resetter = $this->getResetter($client);
$resetter->resetIndexType('index', 'type');
+
+ $type = $this->getType($client);
+ $mapping = $type->getMapping();
+
+ $this->assertNotEmpty($mapping, 'Mapping was populated');
}
/**
@@ -36,6 +51,15 @@ class MappingToElasticaTest extends WebTestCase
return $client->getContainer()->get('fos_elastica.resetter');
}
+ /**
+ * @param Client $client
+ * @return \Elastica\Type
+ */
+ private function getType(Client $client)
+ {
+ return $client->getContainer()->get('fos_elastica.index.index.type');
+ }
+
protected function setUp()
{
parent::setUp();
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
+{
+ public function testIndexableUnknown()
+ {
+ $indexable = new Indexable(array());
+ $index = $indexable->isObjectIndexable('index', 'type', new Entity);
+
+ $this->assertTrue($index);
+ }
+
+ /**
+ * @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 806d0b0..bde8b3e 100644
--- a/composer.json
+++ b/composer.json
@@ -22,6 +22,7 @@
},
"require-dev":{
"doctrine/orm": "~2.2",
+ "doctrine/doctrine-bundle": "~1.2@beta",
"doctrine/mongodb-odm": "1.0.*@beta",
"phpunit/phpunit": "~4.1",
"propel/propel1": "1.6.*",