Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 76 additions & 29 deletions lib/Doctrine/ODM/PHPCR/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ class UnitOfWork
*/
private $originalData = array();

/**
* @var array
*/
private $originalTranslatedData = array();

/**
* @var array
*/
Expand Down Expand Up @@ -712,7 +717,12 @@ public function bindTranslation($document, $locale)
));
}

if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::preBindTranslation)) {
$this->doBindTranslation($document, $locale, $class);
}

private function doBindTranslation($document, $locale, ClassMetadata $class, $supressEvents = false)
{
if (!$supressEvents && $invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::preBindTranslation)) {
$this->eventListenersInvoker->invoke(
$class,
Event::preBindTranslation,
Expand All @@ -726,15 +736,11 @@ public function bindTranslation($document, $locale)

$oid = spl_object_hash($document);

if (empty($this->documentTranslations[$oid])) {
$this->documentTranslations[$oid] = array();
}

foreach ($class->translatableFields as $field) {
$this->documentTranslations[$oid][$locale][$field] = $class->reflFields[$field]->getValue($document);
}

if ($invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::postBindTranslation)) {
if (!$supressEvents && $invoke = $this->eventListenersInvoker->getSubscribedSystems($class, Event::postBindTranslation)) {
$this->eventListenersInvoker->invoke(
$class,
Event::postBindTranslation,
Expand Down Expand Up @@ -1539,24 +1545,64 @@ public function computeChangeSet(ClassMetadata $class, $document)

$fields = array_intersect_key($changeSet, $class->mappings);

if ($this->isDocumentTranslatable($class)) {
$locale = $this->getCurrentLocale($document, $class);
$oid = spl_object_hash($document);

// check if the document has not been bound or if the locale has not yet been bound
// (note array_key_exists is necessary to handle removed translations)
if (empty($this->documentTranslations[$oid])
|| !array_key_exists($locale, $this->documentTranslations[$oid])
) {
$this->doBindTranslation($document, $locale, $class);
}
}

if ($isNew) {
$this->documentChangesets[$oid]['fields'] = $fields;
$this->scheduledInserts[$oid] = $document;
return;
}

// when locale changes, we should keep all field changes
$keepAllChanges = isset($this->documentLocales[$oid]) && $this->documentLocales[$oid]['current'] !== $this->documentLocales[$oid]['original'];
foreach ($fields as $fieldName => $fieldValue) {
$keepChange = $keepAllChanges;
if (!$keepChange) {
if ($fieldValue instanceof ReferenceManyCollection || $fieldValue instanceof ReferrersCollection) {
if ($fieldValue->changed()) {
$keepChange = true;
$translationChanges = false;
if ($this->isDocumentTranslatable($class)) {
$oid = spl_object_hash($document);
if (isset($this->documentTranslations[$oid])) {
foreach ($this->documentTranslations[$oid] as $localeToCheck => $data) {
// a translation was removed
if (empty($data)) {
$translationChanges = true;
break;
}
// a translation was added
if (empty($this->originalTranslatedData[$oid][$localeToCheck])) {
$translationChanges = true;
break;
}
// a translation was changed
foreach ($data as $fieldName => $fieldValue) {
if ($this->originalTranslatedData[$oid][$localeToCheck][$fieldName] !== $fieldValue) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you opt for comparing translation fields with the original value only at this point. what of the idea to check in bindTranslation and not create an entry in documentTranslations if there is no change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this approach we also ensure that if event listeners mess with the data that we detect changes. i also do not see the advantage of doing it in bindTranslation() aside from the fact that the computeChangeSet() method is rather large already

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we would have less copies of the translated content lying around when nothing changed. i don't see how event listeners could mess with the actual translated values in the array.

the only thing we really do need to compare at this time here are the fields of the currently loaded locale, which we do above when iterating over the fields as per metadata. i think right now we even compare the current locale twice, once as part of all fields of the document and once here. as the current locale is also in documentTranslations.

but anyway, i am fine to merge with this state. i can try to refactor afterwards unless you can convince me that this would somehow break event listeners from changing data.

$translationChanges = true;
break;
}
}
} elseif ($this->originalData[$oid][$fieldName] !== $fieldValue) {
}
}

// ensure that locale changes are not considered a change in the document
if ($class->localeMapping && array_key_exists($class->localeMapping, $fields)) {
unset($fields[$class->localeMapping]);
}
}

foreach ($fields as $fieldName => $fieldValue) {
$keepChange = false;
if ($fieldValue instanceof ReferenceManyCollection || $fieldValue instanceof ReferrersCollection) {
if ($fieldValue->changed()) {
$keepChange = true;
}
} elseif ($this->originalData[$oid][$fieldName] !== $fieldValue) {
$keepChange = true;
}

if ($keepChange) {
Expand All @@ -1566,7 +1612,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
}
}

if (!empty($fields)) {
if (!empty($fields) || $translationChanges) {
$this->documentChangesets[$oid]['fields'] = $fields;
$this->originalData[$oid] = $actualData;
$this->scheduledUpdates[$oid] = $document;
Expand Down Expand Up @@ -2967,6 +3013,7 @@ private function unregisterDocument($document)
$this->scheduledReorders[$oid],
$this->scheduledInserts[$oid],
$this->originalData[$oid],
$this->originalTranslatedData[$oid],
$this->documentIds[$oid],
$this->documentState[$oid],
$this->documentTranslations[$oid],
Expand Down Expand Up @@ -3103,6 +3150,7 @@ public function clear()
$this->documentLocales =
$this->nonMappedData =
$this->originalData =
$this->originalTranslatedData =
$this->documentChangesets =
$this->changesetComputed =
$this->scheduledUpdates =
Expand Down Expand Up @@ -3171,23 +3219,15 @@ private function doSaveTranslation($document, NodeInterface $node, $metadata)
return;
}

$locale = $this->getCurrentLocale($document, $metadata);

$oid = spl_object_hash($document);
// handle case for initial persisting
if (empty($this->documentTranslations[$oid])) {
$this->bindTranslation($document, $locale);
// handle case when locale in the mapped property changed
} elseif (isset($this->documentLocales[$oid]['current'])
&& $locale !== $this->documentLocales[$oid]['current']
) {
$this->bindTranslation($document, $locale);
}

if (!empty($this->documentTranslations[$oid])) {
$strategy = $this->dm->getTranslationStrategy($metadata->translator);
foreach ($this->documentTranslations[$oid] as $locale => $data) {
if ($data) {
foreach ($data as $fieldName => $fieldValue) {
$this->originalTranslatedData[$oid][$locale][$fieldName] = $fieldValue;
}

$strategy->saveTranslation($data, $node, $metadata, $locale);
} else {
$strategy->removeTranslation($document, $node, $metadata, $locale);
Expand Down Expand Up @@ -3330,6 +3370,7 @@ public function doLoadTranslation($document, ClassMetadata $metadata, $locale =
}

$currentLocale = $this->getCurrentLocale($document, $metadata);

// if no locale is specified, we reset the current translation
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is the wrong behaviour. we should simply do nothing if there is nothing to do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for resetting, there is DocumentManager::refresh

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fine for me too .. but imho this should be done in a new PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally agreed, does not belong to that bug. that would be issue #556 i think. so lets just say this PR does not fix #556 and i will try to come up with a follow-up PR once this merged.

might go into 1.3 only, guess we have to stop at some point :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the scenario from #556 is fixed and a test is included for this even ..

$locale = $locale ?: $currentLocale;

Expand All @@ -3339,7 +3380,13 @@ public function doLoadTranslation($document, ClassMetadata $metadata, $locale =
$localeUsed = $this->doLoadDatabaseTranslation($document, $metadata, $locale, $fallback, $refresh);
}

$this->setLocale($document, $metadata, $localeUsed);
$this->doBindTranslation($document, $localeUsed, $metadata, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this $supressEvent parameter is a bit ugly

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you are calling doBindTranslation here to store the original data of the translation that was loaded? or what for?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes


$oid = spl_object_hash($document);
foreach ($metadata->translatableFields as $fieldName) {
$this->originalData[$oid][$fieldName] = $this->originalTranslatedData[$oid][$localeUsed][$fieldName]
= $metadata->getFieldValue($document, $fieldName);
}

if ($metadata->parentMapping) {
$parent = $metadata->reflFields[$metadata->parentMapping]->getValue($document);
Expand Down
161 changes: 161 additions & 0 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/ChangesetCalculationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace Doctrine\Tests\ODM\PHPCR\Functional;

use Doctrine\Common\EventArgs;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ODM\PHPCR\Event;
use Doctrine\ODM\PHPCR\Translation\LocaleChooser\LocaleChooser;

class ChangesetCalculationTest extends \Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase
{
/**
* @var ChangesetListener
*/
private $listener;

/**
* @var \Doctrine\ODM\PHPCR\DocumentManager
*/
private $dm;

public function setUp()
{
$this->listener = new ChangesetListener();
$this->dm = $this->createDocumentManager();
$this->dm->setLocaleChooserStrategy(new LocaleChooser(array('en' => array('fr'), 'fr' => array('en')), 'en'));
$this->node = $this->resetFunctionalNode($this->dm);
}

public function testComputeChangeset()
{
$this->dm
->getEventManager()
->addEventListener(
array(
Event::postUpdate,
),
$this->listener
);

// Create initial user
$user1 = new \Doctrine\Tests\Models\CMS\CmsUser();
$user1->name = 'david';
$user1->username = 'dbu';
$user1->status = 'active';
$this->dm->persist($user1);

// Create additional user
$user2 = new \Doctrine\Tests\Models\CMS\CmsUser();
$user2->name = 'lukas';
$user2->username = 'lsmith';
$user2->status = 'active';
$this->dm->persist($user2);

$this->dm->flush();
$this->assertEquals(0, $this->listener->count);
$this->dm->clear();

$user1 = $this->dm->find(null, $user1->id);
$this->dm->find(null, $user2->id);

$user1->status = 'changed';
$this->dm->flush();

$this->assertEquals(1, $this->listener->count);
}

public function testComputeChangesetTranslatable()
{
$this->dm
->getEventManager()
->addEventListener(
array(
Event::postUpdate,
),
$this->listener
);

// Create initial user
$user1 = new \Doctrine\Tests\Models\CMS\CmsUserTranslatable();
$user1->name = 'david';
$user1->username = 'dbu';
$user1->status = 'active';
$this->dm->persist($user1);

// Create additional user
$user2 = new \Doctrine\Tests\Models\CMS\CmsUserTranslatable();
$user2->name = 'lukas';
$user2->username = 'lsmith';
$user2->status = 'active';
$this->dm->persist($user2);

$this->dm->flush();
$this->assertEquals(0, $this->listener->count);
$this->dm->clear();

$user1 = $this->dm->find(null, $user1->id);
$this->dm->find(null, $user2->id);

$user1->status = 'changed';
$this->dm->flush();

$this->assertEquals(1, $this->listener->count);
}

public function testComputeChangesetTranslatableFind()
{
$this->dm
->getEventManager()
->addEventListener(
array(
Event::postUpdate,
),
$this->listener
);

// Create initial user
$user1 = new \Doctrine\Tests\Models\CMS\CmsUserTranslatable();
$user1->name = 'david';
$user1->username = 'dbu';
$user1->status = 'activ';
$this->dm->persist($user1);
$this->dm->bindTranslation($user1, 'en');
$user1->status = 'actif';
$this->dm->bindTranslation($user1, 'fr');

$this->dm->flush();
$this->assertEquals(0, $this->listener->count);
$this->dm->clear();

$user1 = $this->dm->findTranslation(null, $user1->id, 'en');
$this->dm->findTranslation(null, $user1->id, 'fr');

$this->dm->flush();

$this->assertEquals(0, $this->listener->count);

$user1 = $this->dm->findTranslation(null, $user1->id, 'en');
$user1->status = 'active';
$this->dm->findTranslation(null, $user1->id, 'fr');

$this->dm->flush();

$this->assertEquals(1, $this->listener->count);
$this->dm->clear();

$user1 = $this->dm->findTranslation(null, $user1->id, 'en');
$this->assertEquals('active', $user1->status);

}
}

class ChangesetListener
{
public $count = 0;

public function postUpdate(LifecycleEventArgs $e)
{
$this->count++;
}
}