Merge branch 'master' into typed-config
Conflicts: CHANGELOG-3.0.md Client.php DependencyInjection/Configuration.php DependencyInjection/FOSElasticaExtension.php DynamicIndex.php Resources/config/config.xml Resources/config/mongodb.xml Resources/config/orm.xml Tests/DependencyInjection/ConfigurationTest.php composer.json
This commit is contained in:
commit
e78950ddb7
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
*
|
||||
|
|
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)) {
|
||||
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());
|
||||
}
|
||||
}
|
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);
|
||||
}
|
|
@ -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
|
||||
-------------
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
<parameters>
|
||||
<parameter key="fos_elastica.index.class">FOS\ElasticaBundle\Elastica\Index</parameter>
|
||||
<parameter key="fos_elastica.type.class">Elastica\Type</parameter>
|
||||
<parameter key="fos_elastica.indexable.class">FOS\ElasticaBundle\Provider\Indexable</parameter>
|
||||
<parameter key="fos_elastica.index_manager.class">FOS\ElasticaBundle\Index\IndexManager</parameter>
|
||||
<parameter key="fos_elastica.resetter.class">FOS\ElasticaBundle\Index\Resetter</parameter>
|
||||
<parameter key="fos_elastica.finder.class">FOS\ElasticaBundle\Finder\TransformedFinder</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<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.index_prototype" class="%fos_elastica.index.class%" factory-service="fos_elastica.client" factory-method="getIndex" abstract="true">
|
||||
<argument /> <!-- index name -->
|
||||
</service>
|
||||
|
|
|
@ -7,17 +7,18 @@
|
|||
<services>
|
||||
<service id="fos_elastica.provider.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Provider" public="true" abstract="true">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument /> <!-- model -->
|
||||
<argument type="collection" /> <!-- options -->
|
||||
<argument type="service" id="doctrine_mongodb" />
|
||||
</service>
|
||||
|
||||
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument /> <!-- model -->
|
||||
<argument type="service" /> <!-- object persister -->
|
||||
<argument type="collection" /> <!-- events -->
|
||||
<argument/> <!-- identifier -->
|
||||
<tag name="doctrine_mongodb.odm.event_subscriber" />
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument type="collection" /> <!-- configuration -->
|
||||
<argument type="service" on-invalid="null"/> <!-- logger -->
|
||||
</service>
|
||||
|
||||
<service id="fos_elastica.elastica_to_model_transformer.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\ElasticaToModelTransformer" public="false">
|
||||
|
|
|
@ -7,17 +7,18 @@
|
|||
<services>
|
||||
<service id="fos_elastica.provider.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Provider" public="true" abstract="true">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument /> <!-- model -->
|
||||
<argument type="collection" /> <!-- options -->
|
||||
<argument type="service" id="doctrine" />
|
||||
</service>
|
||||
|
||||
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument /> <!-- model -->
|
||||
<argument type="service" /> <!-- object persister -->
|
||||
<argument type="collection" /> <!-- events -->
|
||||
<argument/> <!-- identifier -->
|
||||
<argument /> <!-- check method -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument type="collection" /> <!-- configuration -->
|
||||
<argument type="service" on-invalid="null"/> <!-- logger -->
|
||||
<tag name="doctrine.event_subscriber" />
|
||||
</service>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<services>
|
||||
<service id="fos_elastica.provider.prototype.propel" class="FOS\ElasticaBundle\Propel\Provider" public="true" abstract="true">
|
||||
<argument /> <!-- object persister -->
|
||||
<argument type="service" id="fos_elastica.indexable" />
|
||||
<argument /> <!-- model -->
|
||||
<argument type="collection" /> <!-- options -->
|
||||
</service>
|
||||
|
|
|
@ -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
|
||||
---------------
|
||||
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
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');
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
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'
|
||||
|
99
Tests/Provider/IndexableTest.php
Normal file
99
Tests/Provider/IndexableTest.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?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
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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.*",
|
||||
|
|
Loading…
Reference in a new issue