Merge pull request #608 from merk/populate-indexable
Indexable callback refactoring, implemented callback in Provider
This commit is contained in:
commit
94568d9554
24 changed files with 761 additions and 304 deletions
|
|
@ -12,7 +12,17 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.0...v3.0.1
|
|||
To generate a changelog summary since the last version, run
|
||||
`git log --no-merges --oneline v3.0.0...3.0.x`
|
||||
|
||||
* 3.0.0-ALPHA3 (xxxx-xx-xx)
|
||||
* 3.0.0-ALPHA6 (xxxx-xx-xx)
|
||||
|
||||
* 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.
|
||||
|
||||
* 3.0.0-ALPHA3 (2014-04-01)
|
||||
|
||||
* a9c4c93: Logger is now only enabled in debug mode by default
|
||||
* #463: allowing hot swappable reindexing
|
||||
|
|
|
|||
|
|
@ -186,9 +186,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()
|
||||
|
|
@ -229,7 +243,7 @@ class Configuration implements ConfigurationInterface
|
|||
unset($v[$prop]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
|
|
@ -676,7 +690,6 @@ class Configuration implements ConfigurationInterface
|
|||
->treatTrueLike('fos_elastica.logger')
|
||||
->end()
|
||||
->scalarNode('service')->end()
|
||||
->variableNode('is_indexable_callback')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('finder')
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -68,4 +68,11 @@ interface ObjectPersisterInterface
|
|||
* @param array $identifiers array of domain model object identifiers
|
||||
*/
|
||||
public function deleteManyByIdentifiers(array $identifiers);
|
||||
|
||||
/**
|
||||
* Returns the elastica type used by this persister
|
||||
*
|
||||
* @return \Elastica\Type
|
||||
*/
|
||||
public function getType();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,23 +24,48 @@ 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)
|
||||
{
|
||||
$typeName = $this->objectPersister->getType()->getName();
|
||||
$indexName = $this->objectPersister->getType()->getIndex()->getName();
|
||||
|
||||
return $this->indexable->isObjectIndexable($indexName, $typeName, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string with RAM usage information (current and peak)
|
||||
*
|
||||
|
|
|
|||
174
Provider/Indexable.php
Normal file
174
Provider/Indexable.php
Normal 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());
|
||||
}
|
||||
}
|
||||
25
Provider/IndexableInterface.php
Normal file
25
Provider/IndexableInterface.php
Normal 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);
|
||||
}
|
||||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,24 +12,42 @@ 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);
|
||||
|
||||
$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));
|
||||
$index = $this->getMockBuilder('Elastica\Index')->disableOriginalConstructor()->getMock();
|
||||
$index->expects($this->any())
|
||||
->method('getName')
|
||||
->will($this->returnValue('index'));
|
||||
$type = $this->getMockBuilder('Elastica\Type')->disableOriginalConstructor()->getMock();
|
||||
$type->expects($this->any())
|
||||
->method('getName')
|
||||
->will($this->returnValue('type'));
|
||||
$type->expects($this->any())
|
||||
->method('getIndex')
|
||||
->will($this->returnValue($index));
|
||||
|
||||
$this->objectPersister->expects($this->any())
|
||||
->method('getType')
|
||||
->will($this->returnValue($type));
|
||||
|
||||
$this->managerRegistry->expects($this->any())
|
||||
->method('getManagerForClass')
|
||||
->with($this->objectClass)
|
||||
->will($this->returnValue($this->objectManager));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,14 +80,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();
|
||||
}
|
||||
|
||||
|
|
@ -162,6 +179,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
|
||||
*/
|
||||
|
|
@ -169,6 +216,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,
|
||||
|
|
@ -208,6 +256,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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
51
Tests/Functional/IndexableCallbackTest.php
Normal file
51
Tests/Functional/IndexableCallbackTest.php
Normal 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');
|
||||
}
|
||||
}
|
||||
25
Tests/Functional/TypeObj.php
Normal file
25
Tests/Functional/TypeObj.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -50,4 +50,4 @@ fos_elastica:
|
|||
_parent:
|
||||
type: "parent"
|
||||
property: "parent"
|
||||
identifier: "id"
|
||||
identifier: "id"
|
||||
|
|
|
|||
11
Tests/Functional/app/ORM/bundles.php
Normal file
11
Tests/Functional/app/ORM/bundles.php
Normal 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()
|
||||
);
|
||||
44
Tests/Functional/app/ORM/config.yml
Normal file
44
Tests/Functional/app/ORM/config.yml
Normal 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'
|
||||
|
||||
91
Tests/Provider/IndexableTest.php
Normal file
91
Tests/Provider/IndexableTest.php
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue