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:
Tim Nagel 2014-06-17 22:42:15 +10:00
commit e78950ddb7
30 changed files with 857 additions and 321 deletions

View file

@ -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

View file

@ -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')

View file

@ -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;

View file

@ -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;

View file

@ -2,15 +2,13 @@
namespace FOS\ElasticaBundle\Doctrine;
use Psr\Log\LoggerInterface;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use FOS\ElasticaBundle\Provider\IndexableInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Automatically update ElasticSearch based on changes to the Doctrine source
@ -25,13 +23,6 @@ class Listener implements EventSubscriber
*/
protected $objectPersister;
/**
* Class of the domain model
*
* @var string
*/
protected $objectClass;
/**
* List of subscribed events
*
@ -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
);
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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
View file

@ -0,0 +1,174 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Provider;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class Indexable implements IndexableInterface
{
/**
* An array of raw configured callbacks for all types.
*
* @var array
*/
private $callbacks = array();
/**
* An instance of ExpressionLanguage
*
* @var ExpressionLanguage
*/
private $expressionLanguage;
/**
* An array of initialised callbacks.
*
* @var array
*/
private $initialisedCallbacks = array();
/**
* PropertyAccessor instance
*
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param array $callbacks
*/
public function __construct(array $callbacks)
{
$this->callbacks = $callbacks;
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
/**
* Return whether the object is indexable with respect to the callback.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object)
{
$type = sprintf('%s/%s', $indexName, $typeName);
$callback = $this->getCallback($type, $object);
if (!$callback) {
return true;
}
if ($callback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($callback, array(
'object' => $object,
$this->getExpressionVar($object) => $object
));
}
return is_string($callback)
? call_user_func(array($object, $callback))
: call_user_func($callback, $object);
}
/**
* Builds and initialises a callback.
*
* @param string $type
* @param object $object
* @return mixed
*/
private function buildCallback($type, $object)
{
if (!array_key_exists($type, $this->callbacks)) {
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());
}
}

View file

@ -0,0 +1,25 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Provider;
interface IndexableInterface
{
/**
* Checks if an object passed should be indexable or not.
*
* @param string $indexName
* @param string $typeName
* @param mixed $object
* @return bool
*/
public function isObjectIndexable($indexName, $typeName, $object);
}

View file

@ -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
-------------

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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
---------------

View file

@ -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'
)
)
)
)
)
)
)
)
)
)
));
}
}

View file

@ -11,12 +11,12 @@ abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
public function testObjectInsertedOnPersist()
{
$persister = $this->getMockPersister();
$entity = new Listener\Entity(1);
$persister = $this->getMockPersister($entity, 'index', 'type');
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$indexable = $this->getMockIndexable('index', 'type', $entity, true);
$listener = $this->createListener($persister, get_class($entity), array());
$listener = $this->createListener($persister, array(), $indexable, 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()
{
}
}

View file

@ -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');
}
}
/**

View file

@ -0,0 +1,51 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Tim Nagel <tim@nagel.com.au>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace FOS\ElasticaBundle\Tests\Functional;
/**
* @group functional
*/
class IndexableCallbackTest extends WebTestCase
{
/**
* 2 reasons for this test:
*
* 1) To test that the configuration rename from is_indexable_callback under the listener
* key is respected, and
* 2) To test the Extension's set up of the Indexable service.
*/
public function testIndexableCallback()
{
$client = $this->createClient(array('test_case' => 'ORM'));
/** @var \FOS\ElasticaBundle\Provider\Indexable $in */
$in = $client->getContainer()->get('fos_elastica.indexable');
$this->assertTrue($in->isObjectIndexable('index', 'type', new TypeObj()));
$this->assertFalse($in->isObjectIndexable('index', 'type2', new TypeObj()));
$this->assertFalse($in->isObjectIndexable('index', 'type3', new TypeObj()));
}
protected function setUp()
{
parent::setUp();
$this->deleteTmpDir('ORM');
}
protected function tearDown()
{
parent::tearDown();
$this->deleteTmpDir('ORM');
}
}

View file

@ -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();

View file

@ -0,0 +1,25 @@
<?php
/**
* This file is part of the FOSElasticaBundle project.
*
* (c) Infinite Networks Pty Ltd <http://www.infinite.net.au>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\ElasticaBundle\Tests\Functional;
class TypeObj
{
public function isIndexable()
{
return true;
}
public function isntIndexable()
{
return false;
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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()
{
}
}

View file

@ -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.*",