Merge remote-tracking branch 'upstream/master'

Conflicts:
	Logger/ElasticaLogger.php
	README.md
This commit is contained in:
Ray 2014-03-18 19:59:27 -04:30
commit 41b347dfe4
23 changed files with 481 additions and 206 deletions

View File

@ -12,6 +12,15 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.0...v3.0.1
To generate a changelog summary since the last version, run
`git log --no-merges --oneline v3.0.0...3.0.x`
* 3.0.0-ALPHA2 (2014-xx-xx)
* 3.0.0-ALPHA3 (xxxx-xx-xx)
* #463: allowing hot swappable reindexing
* #415: BC BREAK: document indexing occurs in postFlush rather than the pre* events previously.
* 7d13823: Dropped (broken) support for Symfony <2.3
* 3.0.0-ALPHA2 (2014-03-17)
* 41bf07e: Renamed the `no-stop-on-error` option in PopulateCommand to `ignore-errors`
* 418b9d7: Fixed validation of url configuration
* 726892c: Ignore TypeMissingException when resetting a single type. This allows to create new types without having to recreate the whole index.
* 7f53bad Add support for include_in_{parent,root} for nested and objects

View File

@ -33,4 +33,9 @@ class Client extends ElasticaClient
return $response;
}
public function getIndex($name)
{
return new DynamicIndex($this, $name);
}
}

View File

@ -126,6 +126,7 @@ class PopulateCommand extends ContainerAwareCommand
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->resetter->postPopulate($index);
$this->indexManager->getIndex($index)->refresh();
}

View File

@ -146,6 +146,7 @@ class Configuration implements ConfigurationInterface
->prototype('array')
->children()
->scalarNode('index_name')->end()
->booleanNode('use_alias')->defaultValue(false)->end()
->scalarNode('client')->end()
->scalarNode('finder')
->treatNullLike(true)
@ -183,6 +184,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->scalarNode('persist')->defaultValue('postFlush')->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
@ -275,6 +277,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('insert')->defaultTrue()->end()
->scalarNode('update')->defaultTrue()->end()
->scalarNode('delete')->defaultTrue()->end()
->booleanNode('immediate')->defaultFalse()->end()
->scalarNode('service')->end()
->variableNode('is_indexable_callback')->defaultNull()->end()
->end()
@ -421,6 +424,10 @@ class Configuration implements ConfigurationInterface
}
if (isset($nestings['properties'])) {
$node
->booleanNode('include_in_parent')->end()
->booleanNode('include_in_root')->end()
;
$this->addNestedFieldConfig($node, $nestings, 'properties');
}
}

View File

@ -4,6 +4,7 @@ namespace FOS\ElasticaBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
@ -124,6 +125,7 @@ class FOSElasticaExtension extends Extension
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array(
'index' => new Reference($indexId),
'name_or_alias' => $indexName,
'config' => array(
'mappings' => array()
)
@ -134,6 +136,10 @@ class FOSElasticaExtension extends Extension
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
if ($index['use_alias']) {
$this->indexConfigs[$name]['use_alias'] = true;
}
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig);
}
@ -198,6 +204,10 @@ class FOSElasticaExtension extends Extension
if (isset($type['serializer']['version'])) {
$callbackDef->addMethodCall('setVersion', array($type['serializer']['version']));
}
$callbackClassImplementedInterfaces = class_implements($this->serializerConfig['callback_class']); // PHP < 5.4 friendly
if (isset($callbackClassImplementedInterfaces['Symfony\Component\DependencyInjection\ContainerAwareInterface'])) {
$callbackDef->addMethodCall('setContainer', array(new Reference('service_container')));
}
$container->setDefinition($callbackId, $callbackDef);
@ -443,13 +453,32 @@ class FOSElasticaExtension extends Extension
return $listenerId;
}
/**
* Map Elastica to Doctrine events for the current driver
*/
private function getDoctrineEvents(array $typeConfig)
{
// Flush always calls depending on actions scheduled in lifecycle listeners
$typeConfig['listener']['flush'] = true;
switch ($typeConfig['driver']) {
case 'orm':
$eventsClass = '\Doctrine\ORM\Events';
break;
case 'mongodb':
$eventsClass = '\Doctrine\ODM\MongoDB\Events';
break;
default:
throw new InvalidArgumentException(sprintf('Cannot determine events for driver "%s"', $typeConfig['driver']));
break;
}
$events = array();
$eventMapping = array(
'insert' => array('postPersist'),
'update' => array('postUpdate'),
'delete' => array('postRemove', 'preRemove')
'insert' => array(constant($eventsClass.'::postPersist')),
'update' => array(constant($eventsClass.'::postUpdate')),
'delete' => array(constant($eventsClass.'::preRemove')),
'flush' => array($typeConfig['listener']['immediate'] ? constant($eventsClass.'::preFlush') : constant($eventsClass.'::postFlush'))
);
foreach ($eventMapping as $event => $doctrineEvents) {

View File

@ -2,15 +2,15 @@
namespace FOS\ElasticaBundle\Doctrine;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\ObjectManager;
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;
abstract class AbstractListener implements EventSubscriber
class Listener implements EventSubscriber
{
/**
* Object persister
@ -48,11 +48,11 @@ abstract class AbstractListener implements EventSubscriber
protected $isIndexableCallback;
/**
* Objects scheduled for removal
*
* @var array
* Objects scheduled for insertion, replacement, or removal
*/
private $scheduledForRemoval = array();
public $scheduledForInsertion = array();
public $scheduledForUpdate = array();
public $scheduledForDeletion = array();
/**
* An instance of ExpressionLanguage
@ -149,37 +149,6 @@ abstract class AbstractListener implements EventSubscriber
: call_user_func($this->isIndexableCallback, $object);
}
/**
* Schedules the object for removal.
*
* This is usually called during the pre-remove event.
*
* @param object $object
* @param ObjectManager $objectManager
*/
protected function scheduleForRemoval($object, ObjectManager $objectManager)
{
$metadata = $objectManager->getClassMetadata($this->objectClass);
$esId = $metadata->getFieldValue($object, $this->esIdentifierField);
$this->scheduledForRemoval[spl_object_hash($object)] = $esId;
}
/**
* Removes the object if it was scheduled for removal.
*
* This is usually called during the post-remove event.
*
* @param object $object
*/
protected function removeIfScheduled($object)
{
$objectHash = spl_object_hash($object);
if (isset($this->scheduledForRemoval[$objectHash])) {
$this->objectPersister->deleteById($this->scheduledForRemoval[$objectHash]);
unset($this->scheduledForRemoval[$objectHash]);
}
}
/**
* @param mixed $object
* @return string
@ -207,4 +176,70 @@ abstract class AbstractListener implements EventSubscriber
return $this->expressionLanguage;
}
public function postPersist(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
$this->scheduledForInsertion[] = $entity;
}
}
public function postUpdate(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate[] = $entity;
} else {
// Delete if no longer indexable
$this->scheduledForDeletion[] = $entity;
}
}
}
public function preRemove(EventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->scheduledForDeletion[] = $entity;
}
}
/**
* Persist scheduled objects to ElasticSearch
*/
private function persistScheduled()
{
if (count($this->scheduledForInsertion)) {
$this->objectPersister->insertMany($this->scheduledForInsertion);
}
if (count($this->scheduledForUpdate)) {
$this->objectPersister->replaceMany($this->scheduledForUpdate);
}
if (count($this->scheduledForDeletion)) {
$this->objectPersister->deleteMany($this->scheduledForDeletion);
}
}
/**
* Iterate through scheduled actions before flushing to emulate 2.x behavior. Note that the ElasticSearch index
* will fall out of sync with the source data in the event of a crash during flush.
*/
public function preFlush(EventArgs $eventArgs)
{
$this->persistScheduled();
}
/**
* Iterating through scheduled actions *after* flushing ensures that the ElasticSearch index will be affected
* only if the query is successful
*/
public function postFlush(EventArgs $eventArgs)
{
$this->persistScheduled();
}
}

View File

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\MongoDB;
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass && $this->isObjectIndexable($document)) {
$this->objectPersister->insertOne($document);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
if ($this->isObjectIndexable($document)) {
$this->objectPersister->replaceOne($document);
} else {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
$this->removeIfScheduled($document);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->scheduleForRemoval($document, $eventArgs->getDocumentManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$document = $eventArgs->getDocument();
if ($document instanceof $this->objectClass) {
$this->removeIfScheduled($document);
}
}
}

View File

@ -1,50 +0,0 @@
<?php
namespace FOS\ElasticaBundle\Doctrine\ORM;
use Doctrine\ORM\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Doctrine\AbstractListener;
class Listener extends AbstractListener
{
public function postPersist(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass && $this->isObjectIndexable($entity)) {
$this->objectPersister->insertOne($entity);
}
}
public function postUpdate(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
if ($this->isObjectIndexable($entity)) {
$this->objectPersister->replaceOne($entity);
} else {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
$this->removeIfScheduled($entity);
}
}
}
public function preRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
}
}
public function postRemove(LifecycleEventArgs $eventArgs)
{
$entity = $eventArgs->getEntity();
if ($entity instanceof $this->objectClass) {
$this->removeIfScheduled($entity);
}
}
}

28
DynamicIndex.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace FOS\ElasticaBundle;
use Elastica\Index;
/**
* Elastica index capable of reassigning name dynamically
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
*/
class DynamicIndex extends Index
{
/**
* Reassign index name
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
*
* @return void
*/
public function overrideName($name)
{
$this->_name = $name;
}
}

View File

@ -14,32 +14,42 @@ use Psr\Log\LoggerInterface;
*/
class ElasticaLogger implements LoggerInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
protected $queries;
/**
* @var array
*/
protected $queries = array();
/**
* @var boolean
*/
protected $debug;
/**
* Constructor.
*
* @param LoggerInterface|null $logger The Symfony logger
* @param bool $debug
* @param boolean $debug
*/
public function __construct(LoggerInterface $logger = null, $debug = false)
{
$this->logger = $logger;
$this->queries = array();
$this->debug = $debug;
}
/**
* Logs a query.
*
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data arguments
* @param float $time execution time
* @param array $connection host, port, transport and headers of the query
* @param array $query arguments
* @param string $path Path to call
* @param string $method Rest method to use (GET, POST, DELETE, PUT)
* @param array $data Arguments
* @param float $time Execution time
* @param array $connection Host, port, transport, and headers of the query
* @param array $query Arguments
*/
public function logQuery($path, $method, $data, $time, $connection = array(), $query = array())
{

View File

@ -33,7 +33,7 @@ class RawPaginatorAdapter implements PaginatorAdapterInterface
private $totalHits;
/**
* @array for the facets
* @var array for the facets
*/
private $facets;

View File

@ -83,12 +83,12 @@ class ObjectPersister implements ObjectPersisterInterface
} catch (NotFoundException $e) {}
}
/**
* Inserts an array of objects in the type
* Bulk insert an array of objects in the type for the given method
*
* @param array $objects array of domain model objects
**/
* @param string Method to call
*/
public function insertMany(array $objects)
{
$documents = array();
@ -98,6 +98,34 @@ class ObjectPersister implements ObjectPersisterInterface
$this->type->addDocuments($documents);
}
/**
* Bulk updates an array of objects in the type
*
* @param array $objects array of domain model objects
*/
public function replaceMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
$this->type->updateDocuments($documents);
}
/**
* Bulk deletes an array of objects in the type
*
* @param array $objects array of domain model objects
*/
public function deleteMany(array $objects)
{
$documents = array();
foreach ($objects as $object) {
$documents[] = $this->transformToElasticaDocument($object);
}
$this->type->deleteDocuments($documents);
}
/**
* Transforms an object to an elastica document
*
@ -108,4 +136,4 @@ class ObjectPersister implements ObjectPersisterInterface
{
return $this->transformer->transform($object, $this->fields);
}
}
}

View File

@ -38,13 +38,27 @@ interface ObjectPersisterInterface
* @param mixed $id
*
* @return null
**/
*/
function deleteById($id);
/**
* Inserts an array of objects in the type
* Bulk inserts an array of objects in the type
*
* @param array $objects array of domain model objects
**/
*/
function insertMany(array $objects);
/**
* Bulk updates an array of objects in the type
*
* @param array $objects array of domain model objects
*/
function replaceMany(array $objects);
/**
* Bulk deletes an array of objects in the type
*
* @param array $objects array of domain model objects
*/
function deleteMany(array $objects);
}

View File

@ -76,6 +76,8 @@ A sample configuration with Basic HTTP Authentication is:
headers:
Authorization: "Basic jdumrGK7rY9TMuQOPng7GZycmxyMHNoir=="
A client configuration can also override the Elastica logger to change the used class ```logger: <your logger class>``` or to simply disable it ```logger: false```. Disabling the logger should be done on production because it can cause a memory leak.
#### Declare a serializer
Elastica can handle objects instead of data arrays if a serializer callable is configured
@ -601,7 +603,11 @@ class User
### Realtime, selective index update
If you use the Doctrine integration, you can let ElasticaBundle update the indexes automatically
when an object is added, updated or removed. It uses Doctrine lifecycle events.
when an object is added, updated or removed. It uses Doctrine lifecycle events to schedule updates
and then synchronizes changes either before or after flush.
> **Propel** doesn't support this feature yet.
Declare that you want to update the index in real time:
fos_elastica:
@ -617,7 +623,7 @@ Declare that you want to update the index in real time:
persistence:
driver: orm
model: Application\UserBundle\Entity\User
listener: ~ # by default, listens to "insert", "update" and "delete"
listener: ~ # by default, listens to "insert", "update" and "delete" and updates `postFlush`
Now the index is automatically updated each time the state of the bound Doctrine repository changes.
No need to repopulate the whole "user" index when a new `User` is created.
@ -630,7 +636,19 @@ You can also choose to only listen for some of the events:
update: false
delete: true
> **Propel** doesn't support this feature yet.
By default, the ElasticSearch index will be updated after flush. To update before flushing, set `immediate`
to `true`:
persistence:
listener:
insert: true
update: false
delete: true
immediate: true
> Using `immediate` to update ElasticSearch before flush completes may cause the ElasticSearch index to fall out of
> sync with the source database in the event of a crash during the flush itself, such as in the case of a bad query.
### Checking an entity method for listener

View File

@ -2,6 +2,9 @@
namespace FOS\ElasticaBundle;
use Elastica\Exception\ExceptionInterface;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
/**
@ -26,8 +29,8 @@ class Resetter
*/
public function resetAllIndexes()
{
foreach ($this->indexConfigsByName as $indexConfig) {
$indexConfig['index']->create($indexConfig['config'], true);
foreach (array_keys($this->indexConfigsByName) as $name) {
$this->resetIndex($name);
}
}
@ -40,7 +43,17 @@ class Resetter
public function resetIndex($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$indexConfig['index']->create($indexConfig['config'], true);
$esIndex = $indexConfig['index'];
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$name = $indexConfig['name_or_alias'];
$name .= uniqid();
$esIndex->overrideName($name);
$esIndex->create($indexConfig['config']);
return;
}
$esIndex->create($indexConfig['config'], true);
}
/**
@ -59,7 +72,13 @@ class Resetter
}
$type = $indexConfig['index']->getType($typeName);
$type->delete();
try {
$type->delete();
} catch (ResponseException $e) {
if (strpos($e->getMessage(), 'TypeMissingException') === false) {
throw $e;
}
}
$mapping = $this->createMapping($indexConfig['config']['mappings'][$typeName]);
$type->setMapping($mapping);
}
@ -105,4 +124,122 @@ class Resetter
return $this->indexConfigsByName[$indexName];
}
public function postPopulate($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$this->switchIndexAlias($indexName);
}
}
/**
* Switches the alias for given index (by key) to the newly populated index
* and deletes the old index
*
* @param string $indexName Index name
*
* @throws \RuntimeException
*/
private function switchIndexAlias($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$esIndex = $indexConfig['index'];
$aliasName = $indexConfig['name_or_alias'];
$oldIndexName = false;
$newIndexName = $esIndex->getName();
$aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName);
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(
sprintf(
'Alias %s is used for multiple indexes: [%s].
Make sure it\'s either not used or is assigned to one index only',
$aliasName,
join(', ', $aliasedIndexes)
)
);
}
// Change the alias to point to the new index
// Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic
// In 0.20.x it's atomic, but it doesn't return the old index name
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) == 1) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName)
);
try {
$esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$esIndex->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getError()
);
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
)
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($esIndex->getClient(), $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
)
);
}
}
}
/**
* Returns array of indexes which are mapped to given alias
*
* @param Index $esIndex ES Index
* @param string $aliasName Alias name
*
* @return array
*/
private function getAliasedIndexes(Index $esIndex, $aliasName)
{
$aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
return $aliasedIndexes;
}
}

View File

@ -6,7 +6,7 @@
<parameters>
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Client</parameter>
<parameter key="fos_elastica.index.class">Elastica\Index</parameter>
<parameter key="fos_elastica.index.class">FOS\ElasticaBundle\DynamicIndex</parameter>
<parameter key="fos_elastica.type.class">Elastica\Type</parameter>
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>

View File

@ -13,7 +13,7 @@
<argument type="service" id="doctrine_mongodb" />
</service>
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\MongoDB\Listener" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.mongodb" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->

View File

@ -13,7 +13,7 @@
<argument type="service" id="doctrine" />
</service>
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\ORM\Listener" public="false" abstract="true">
<service id="fos_elastica.listener.prototype.orm" class="FOS\ElasticaBundle\Doctrine\Listener" public="false">
<argument /> <!-- object persister -->
<argument /> <!-- model -->
<argument type="collection" /> <!-- events -->

View File

@ -3,9 +3,11 @@
namespace FOS\ElasticaBundle\Tests\Doctrine;
/**
* See concrete MongoDB/ORM instances of this abstract test
*
* @author Richard Miller <info@limethinking.co.uk>
*/
abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
abstract class ListenerTest extends \PHPUnit_Framework_TestCase
{
public function testObjectInsertedOnPersist()
{
@ -14,12 +16,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->once())
->method('insertOne')
->with($entity);
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postPersist($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForInsertion));
$persister->expects($this->once())
->method('insertMany')
->with($listener->scheduledForInsertion);
$listener->postFlush($eventArgs);
}
/**
@ -32,12 +38,18 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1, false);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->never())
->method('insertOne');
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener->postPersist($eventArgs);
$this->assertEmpty($listener->scheduledForInsertion);
$persister->expects($this->never())
->method('insertOne');
$persister->expects($this->never())
->method('insertMany');
$listener->postFlush($eventArgs);
}
public function testObjectReplacedOnUpdate()
@ -47,15 +59,18 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
$entity = new Listener\Entity(1);
$eventArgs = $this->createLifecycleEventArgs($entity, $this->getMockObjectManager());
$persister->expects($this->once())
->method('replaceOne')
->with($entity);
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postUpdate($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForUpdate));
$persister->expects($this->once())
->method('replaceMany')
->with(array($entity));
$persister->expects($this->never())
->method('deleteById');
$listener = $this->createListener($persister, get_class($entity), array());
$listener->postUpdate($eventArgs);
$listener->postFlush($eventArgs);
}
/**
@ -80,16 +95,20 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$persister->expects($this->never())
->method('replaceOne');
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array());
$listener->setIsIndexableCallback($isIndexableCallback);
$listener->postUpdate($eventArgs);
$this->assertEmpty($listener->scheduledForUpdate);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->never())
->method('replaceOne');
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
public function testObjectDeletedOnRemove()
@ -111,13 +130,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'id')
->will($this->returnValue($entity->getId()));
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array());
$listener->preRemove($eventArgs);
$listener->postRemove($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
public function testObjectWithNonStandardIdentifierDeletedOnRemove()
@ -139,13 +161,16 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
->with($entity, 'identifier')
->will($this->returnValue($entity->getId()));
$persister->expects($this->once())
->method('deleteById')
->with($entity->getId());
$listener = $this->createListener($persister, get_class($entity), array(), 'identifier');
$listener->preRemove($eventArgs);
$listener->postRemove($eventArgs);
$this->assertEquals($entity, current($listener->scheduledForDeletion));
$persister->expects($this->once())
->method('deleteMany')
->with(array($entity));
$listener->postFlush($eventArgs);
}
/**

View File

@ -2,9 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Doctrine\MongoDB;
use FOS\ElasticaBundle\Tests\Doctrine\AbstractListenerTest;
use FOS\ElasticaBundle\Tests\Doctrine\ListenerTest as BaseListenerTest;
class ListenerTest extends AbstractListenerTest
class ListenerTest extends BaseListenerTest
{
public function setUp()
{
@ -25,7 +25,7 @@ class ListenerTest extends AbstractListenerTest
protected function getListenerClass()
{
return 'FOS\ElasticaBundle\Doctrine\MongoDB\Listener';
return 'FOS\ElasticaBundle\Doctrine\Listener';
}
protected function getObjectManagerClass()

View File

@ -2,9 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Doctrine\ORM;
use FOS\ElasticaBundle\Tests\Doctrine\AbstractListenerTest;
use FOS\ElasticaBundle\Tests\Doctrine\ListenerTest as BaseListenerTest;
class ListenerTest extends AbstractListenerTest
class ListenerTest extends BaseListenerTest
{
public function setUp()
{
@ -25,7 +25,7 @@ class ListenerTest extends AbstractListenerTest
protected function getListenerClass()
{
return 'FOS\ElasticaBundle\Doctrine\ORM\Listener';
return 'FOS\ElasticaBundle\Doctrine\Listener';
}
protected function getObjectManagerClass()

View File

@ -2,6 +2,9 @@
namespace FOS\ElasticaBundle\Tests\Resetter;
use Elastica\Exception\ResponseException;
use Elastica\Request;
use Elastica\Response;
use FOS\ElasticaBundle\Resetter;
use Elastica\Type\Mapping;
@ -130,6 +133,32 @@ class ResetterTest extends \PHPUnit_Framework_TestCase
$resetter->resetIndexType('foo', 'c');
}
public function testResetIndexTypeIgnoreTypeMissingException()
{
$type = $this->getMockElasticaType();
$this->indexConfigsByName['foo']['index']->expects($this->once())
->method('getType')
->with('a')
->will($this->returnValue($type));
$type->expects($this->once())
->method('delete')
->will($this->throwException(new ResponseException(
new Request(''),
new Response(array('error' => 'TypeMissingException[[de_20131022] type[bla] missing]', 'status' => 404)))
));
$mapping = Mapping::create($this->indexConfigsByName['foo']['config']['mappings']['a']['properties']);
$mapping->setParam('dynamic_templates', $this->indexConfigsByName['foo']['config']['mappings']['a']['dynamic_templates']);
$type->expects($this->once())
->method('setMapping')
->with($mapping);
$resetter = new Resetter($this->indexConfigsByName);
$resetter->resetIndexType('foo', 'a');
}
public function testIndexMappingForParent()
{
$type = $this->getMockElasticaType();

View File

@ -12,11 +12,11 @@
],
"require": {
"php": ">=5.3.2",
"symfony/framework-bundle": "~2.1",
"symfony/framework-bundle": "~2.3",
"symfony/console": "~2.1",
"symfony/form": "~2.1",
"symfony/property-access": "~2.2",
"ruflin/elastica": "~0.20",
"ruflin/elastica": ">=0.20, <1.1-dev",
"psr/log": "~1.0"
},
"require-dev":{