Permalink
Browse files

Fixed issues with updating embedded objects.

An example fixed use case would be if a session-embeddable document Foo embeds many of another document Bar. Previously, this would result in multiple Bars being inserted into the Bar-collection of Foo.
  • Loading branch information...
1 parent 2302e6f commit 5a66dccd6ed4a1ee4fd4acf47c4a401e74e78300 @magnusnordlander magnusnordlander committed Jan 20, 2012
@@ -0,0 +1,86 @@
+<?php
+
+namespace Ebutik\MongoSessionBundle\Comparison;
+
+/**
+*
+*/
+class SessionEmbeddableDocumentsComparison
+{
+ protected $old_wrappers;
+ protected $new_documents;
+
+ protected $old_object_wrappers = array();
+ protected $old_object_hashes = array();
+ protected $wrappers_by_hash = array();
+
+ protected $new_object_hashes;
+
+ protected $removed_embeddable_hashes;
+ protected $added_embeddable_hashes;
+
+ public function __construct(array $old_wrappers, array $new_documents)
+ {
+ $this->old_wrappers = $old_wrappers;
+ $this->new_documents = $new_documents;
+
+ foreach ($this->old_wrappers as $wrapper)
+ {
+ $hash = spl_object_hash($wrapper->getAttribute());
+
+ $this->old_object_wrappers[$wrapper->getKey()] = $wrapper;
+ $this->old_object_hashes[$wrapper->getKey()] = $hash;
+ $this->wrappers_by_hash[$hash] = $wrapper;
+ }
+
+ $this->new_object_hashes = array_map('spl_object_hash', $new_documents);
+
+ $this->removed_embeddable_hashes = array_diff(array_values($this->old_object_hashes), array_values($this->new_object_hashes));
+ $this->added_embeddable_hashes = array_diff(array_values($this->new_object_hashes), array_values($this->old_object_hashes));
+
+ }
+
+ public function getRemovedWrappers()
+ {
+ $wrappers_by_hash = $this->wrappers_by_hash;
+ return array_map(function($hash) use ($wrappers_by_hash)
+ {
+ return $wrappers_by_hash[$hash];
+ }, $this->removed_embeddable_hashes);
+ }
+
+ public function getAddedKeyDocumentArray()
+ {
+ $flipped_new_hashes = array_flip($this->new_object_hashes);
+
+ $key_document_array = array();
+
+ foreach ($this->added_embeddable_hashes as $hash)
+ {
+ $key = $flipped_new_hashes[$hash];
+ $document = $this->new_documents[$key];
+
+ $key_document_array[$key] = $document;
+ }
+
+ return $key_document_array;
+ }
+
+ public function getKeyUpdateTranslationArray()
+ {
+ $old_keys_by_hash = array_diff_key(array_flip($this->old_object_hashes), array_flip($this->removed_embeddable_hashes));
+ $new_keys_by_hash = array_diff_key(array_flip($this->new_object_hashes), array_flip($this->added_embeddable_hashes));
+
+ $translation_array = array();
+
+ foreach ($old_keys_by_hash as $hash => $key)
+ {
+ if ($new_keys_by_hash[$hash] != $key)
+ {
+ $translation_array[$key] = $new_keys_by_hash[$hash];
+ }
+ }
+
+ return $translation_array;
+ }
+}
@@ -31,6 +31,11 @@ public function __construct($key, SessionEmbeddable $attribute)
$this->attribute = $attribute;
}
+ public function setKey($key)
+ {
+ $this->key = $key;
+ }
+
/**
* @author Magnus Nordlander
**/
@@ -4,6 +4,8 @@
use Ebutik\MongoSessionBundle\Collection\FlatteningParameterBag;
+use Ebutik\MongoSessionBundle\Comparison\SessionEmbeddableDocumentsComparison;
+
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Doctrine\Common\Collections\ArrayCollection;
@@ -61,22 +63,55 @@ public function __clone()
// otherwise do nothing, do NOT throw an exception!
}
- protected function clear()
+ protected function clearHashes()
{
$this->scalar_attributes = array();
$this->serialized_attributes = array();
- $this->embeddable_attributes->clear();
+ }
+
+ protected function getWrapperForKey($key)
+ {
+ foreach ($this->embeddable_attributes as $wrapper)
+ {
+ if ($wrapper->getKey() == $key)
+ {
+ return $wrapper;
+ }
+ }
+ }
+
+ protected function updateEmbeddables(array $embeddables)
+ {
+ $comparison = new SessionEmbeddableDocumentsComparison($this->embeddable_attributes->toArray(), $embeddables);
+
+ foreach ($comparison->getRemovedWrappers() as $wrapper)
+ {
+ $this->embeddable_attributes->removeElement($wrapper);
+ }
+
+ foreach ($comparison->getAddedKeyDocumentArray() as $key => $document)
+ {
+ $this->embeddable_attributes->add(new EmbeddableSessionAttributeWrapper($key, $document));
+ }
+
+ foreach ($comparison->getKeyUpdateTranslationArray() as $old => $new)
+ {
+ $this->getWrapperForKey($old)->setKey($new);
+ }
}
protected function _write(array $data)
{
- $this->clear();
+ // We can't do this to embeds, because that makes Doctrine a sad panda.
+ $this->clearHashes();
+
+ $embeddables = array();
foreach ($data as $key => $subdata)
{
if ($subdata instanceOf SessionEmbeddable)
{
- $this->embeddable_attributes->add(new EmbeddableSessionAttributeWrapper($key, $subdata));
+ $embeddables[$key] = $subdata;
}
else if (is_scalar($subdata) || $subdata === null)
{
@@ -88,9 +123,11 @@ protected function _write(array $data)
}
else
{
- throw new \RuntimeError("Data of type ".gettype($subdata)." cannot be saved in the session");
+ throw new \RuntimeException("Data of type ".gettype($subdata)." cannot be saved in the session");
}
}
+
+ $this->updateEmbeddables($embeddables);
}
protected function getKeyValueArrayForEmbeddedObjects()
@@ -0,0 +1,102 @@
+<?php
+
+namespace Ebutik\MongoSessionBundle\Tests\Comparison;
+
+use Ebutik\MongoSessionBundle\Comparison\SessionEmbeddableDocumentsComparison;
+use Ebutik\MongoSessionBundle\Document\EmbeddableSessionAttributeWrapper;
+
+use Mockery as M;
+
+class SessionEmbeddableDocumentsComparisonFunctionalTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->obj1 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj2 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj3 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj4 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj5 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj6 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+ $this->obj7 = M::mock('Ebutik\MongoSessionBundle\Interfaces\SessionEmbeddable');
+
+ $this->wrapper1 = new EmbeddableSessionAttributeWrapper('obj1', $this->obj1);
+ $this->wrapper2 = new EmbeddableSessionAttributeWrapper('obj2', $this->obj2);
+ $this->wrapper3 = new EmbeddableSessionAttributeWrapper('obj3', $this->obj3);
+ $this->wrapper4 = new EmbeddableSessionAttributeWrapper('obj4', $this->obj4);
+ $this->wrapper5 = new EmbeddableSessionAttributeWrapper('obj5', $this->obj5);
+ $this->wrapper6 = new EmbeddableSessionAttributeWrapper('obj6', $this->obj6);
+ $this->wrapper7 = new EmbeddableSessionAttributeWrapper('obj7', $this->obj7);
+ }
+
+ public function testSameObjects()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('obj1' => $this->obj1, 'obj2' => $this->obj2, 'obj3' => $this->obj3, 'obj4' => $this->obj4, 'obj5' => $this->obj5);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEmpty($comparison->getRemovedWrappers());
+ $this->assertEmpty($comparison->getAddedKeyDocumentArray());
+ $this->assertEmpty($comparison->getKeyUpdateTranslationArray());
+ }
+
+ public function testAddedObjects()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('obj1' => $this->obj1, 'obj2' => $this->obj2, 'obj3' => $this->obj3, 'obj4' => $this->obj4, 'obj5' => $this->obj5, 'obj6' => $this->obj6, 'obj7' => $this->obj7);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEmpty($comparison->getRemovedWrappers());
+ $this->assertEquals(array('obj6' => $this->obj6, 'obj7' => $this->obj7), $comparison->getAddedKeyDocumentArray());
+ $this->assertEmpty($comparison->getKeyUpdateTranslationArray());
+ }
+
+ public function testRemovedObjects()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('obj3' => $this->obj3, 'obj4' => $this->obj4, 'obj5' => $this->obj5);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEquals(array($this->wrapper1, $this->wrapper2), $comparison->getRemovedWrappers());
+ $this->assertEmpty($comparison->getAddedKeyDocumentArray());
+ $this->assertEmpty($comparison->getKeyUpdateTranslationArray());
+ }
+
+ public function testAddedAndRemovedObjects()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('obj3' => $this->obj3, 'obj4' => $this->obj4, 'obj5' => $this->obj5, 'obj6' => $this->obj6, 'obj7' => $this->obj7);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEquals(array($this->wrapper1, $this->wrapper2), $comparison->getRemovedWrappers());
+ $this->assertEquals(array('obj6' => $this->obj6, 'obj7' => $this->obj7), $comparison->getAddedKeyDocumentArray());
+ $this->assertEmpty($comparison->getKeyUpdateTranslationArray());
+ }
+
+ public function testKeysUpdated()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('obj1' => $this->obj1, 'objfoo' => $this->obj2, 'obj3' => $this->obj3, 'obj4' => $this->obj4, 'objbar' => $this->obj5);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEmpty($comparison->getRemovedWrappers());
+ $this->assertEmpty($comparison->getAddedKeyDocumentArray());
+ $this->assertEquals(array('obj2' => 'objfoo', 'obj5' => 'objbar'), $comparison->getKeyUpdateTranslationArray());
+ }
+
+ public function testAddedAndRemovedObjectsKeysUpdated()
+ {
+ $wrappers = array($this->wrapper1, $this->wrapper2, $this->wrapper3, $this->wrapper4, $this->wrapper5);
+ $new_objs = array('objfoo' => $this->obj3, 'obj4' => $this->obj4, 'objbar' => $this->obj5, 'obj6' => $this->obj6, 'obj7' => $this->obj7);
+
+ $comparison = new SessionEmbeddableDocumentsComparison($wrappers, $new_objs);
+
+ $this->assertEquals(array($this->wrapper1, $this->wrapper2), $comparison->getRemovedWrappers());
+ $this->assertEquals(array('obj6' => $this->obj6, 'obj7' => $this->obj7), $comparison->getAddedKeyDocumentArray());
+ $this->assertEquals(array('obj3' => 'objfoo', 'obj5' => 'objbar'), $comparison->getKeyUpdateTranslationArray());
+ }
+}

0 comments on commit 5a66dcc

Please sign in to comment.