Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

fixes #PHPCR-78 #158

Merged
merged 8 commits into from

4 participants

@pitpit

This is a bugfix for http://www.doctrine-project.org/jira/browse/PHPCR-78 but I'm really not sure to did it the best way....

@travisbot

This pull request passes (merged 3f72527 into fc47314).

@lsmith77
Owner

i wonder if in the node type definition we can find the protected properties so that we do not need to hardcode them. @dbu is on vacation but maybe @adou600 knows this too?

@dbu
Collaborator

yes, that would be the most generic way. the code would look about this:

if ($node->hasProperty($class->fieldMappings[$fieldName]['name']) {
    $property = $node->getProperty($class->fieldMappings[$fieldName]['name']);
    $definition = $property->getDefinition();
    if ($definition->isProtected()) {
        continue;
    }

@pitpit can you try this? would be awesome if you can add a test too, to make sure it actually works. i.e. manually changing the create date.

hm, one question: why is the date changed in the first place? do we want to silently ignore users trying to change a protected property or should this throw an error so the user may learn it is not possible to change that property?

@pitpit

Hi,
I updated the isssue http://www.doctrine-project.org/jira/browse/PHPCR-78 and I'm working on a reliable test to make it clear.
@dbu I'll try your code asap

@pitpit

I added in my last commit a functional test to reproduce the bug.

@dbu The code you provide does not work because in this case, Jackalope\Property::getDefinition() throws a \Jackalope\NotImplementedException

here's the exact code I tested (from L1457 to L1472 in Doctrine\ODM\PHPCR\UnitOfWOrk):

               if ($class->fieldMappings[$fieldName]['multivalue']) {
                    $value = $fieldValue === null ? null : $fieldValue->toArray();
                    $node->setProperty($class->fieldMappings[$fieldName]['name'], $value, $type);
                } else if ($node->hasProperty($class->fieldMappings[$fieldName]['name'])) {
                    $property = $node->getProperty($class->fieldMappings[$fieldName]['name']);
                    try { //TODO: remove this try-catch when Jackalope\Property::getDefinition() will be able to acquire definition 
                        $definition = $property->getDefinition();
                        if (!$definition->isProtected()) {
                            $node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type);
                        }
                    } catch (\Jackalope\NotImplementedException $e) {
                        $node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type);
                    }
                } else {
                    $node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type);
                }

The other way could be to simply ignore ConstraintViolationException replacing L1461:

$node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type);

by:

$node->setProperty($class->fieldMappings[$fieldName]['name'], $fieldValue, $type, false);

@lsmith77 what do you thing about that ?

@travisbot

This pull request passes (merged a7e1ba8 into 11b721d).

@travisbot

This pull request fails (merged 8df754a into 11b721d).

@pitpit

Travis seems to fails because of a timeout...

@dbu
Collaborator

the false parameter to not throw exceptions is an internal thing of jackalope and not part of phpcr, so no option.

i try to implement the Property::getDefinition() method, should be able to push tomorrow.

what about the issue that this will silently ignore changes to protected properties? this might create hard to debug confusing situations for users not aware that they try to change a protected property.

@pitpit

The bug we are facing only happens when we map a reference (@ReferenceMany or @ReferenceOne) field into a document. We could gently ignore the constraint violation to avoid it but -like you said- users won't be notified anymore if they try to change a protected property...
The problem is that I don't find why phpcr-odm try to update protected properties only when a reference field is mapped in metadata...
@lsmith77 any idea ?

@lsmith77
Owner

I dont think its an issue that we silently ignore such changes. If we ever get logging integrated we could log this, but otherwise I would hope that someone that expects these to change will test and find that they dont get updated.

As for why this issue only happens if there is a reference mapping, I dont know.

@dbu
Collaborator

ok, Property::getDefinition() is now implemented in jackalope. can you try if it works for you and if so, remove the try-catch statement around it?

@pitpit

Okay, I found the bug.

When there's an association field is mapped in a document, UnitOfWork schedules an update (L1418) on the whole document even if the document is new.

When executeUpdates() is called with scheduled association updates (L1270), every fields are considered as updated fields because they all appear in $this->documentChangesets[$oid]['fields'] (L1476) .

Protected fields like 'jcr:created' are set to null. So phpcr-odm try to push a null value to a protected field in Jackrabbit.

Then we got the "Can't remove the protected property" exception....

I pushed a commit that should fix this bug.

Sorry @dbu we won't need Property::getDefinition() for now

lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -1427,6 +1434,10 @@ private function executeInserts($documents)
$this->evm->dispatchEvent(Event::postPersist, new LifecycleEventArgs($document, $this->dm));
}
}
+
+ foreach($associationChangesets as $oid => $changeset) {
@lsmith77 Owner

missing a space after foreach

@pitpit
pitpit added a note

fixed in d0a07da

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -1415,6 +1417,11 @@ private function executeInserts($documents)
}
} elseif (isset($class->associationsMappings[$fieldName])) {
$this->scheduledAssociationUpdates[$oid] = $document;
+
+ //populate $associationChangesets to force executeUpdates($this->scheduledAssociationUpdates) to only update association fields
+ $data = isset($associationChangesets[$oid]['fields'])?$associationChangesets[$oid]['fields']:array();
@lsmith77 Owner

can you add some spaces around ? and :?

@pitpit
pitpit added a note

fixed in d0a07da

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dbu
Collaborator

@pitpit: np, much better that you found the actual root of the problem and fixed it! probably would create all sorts of weird issues in other situations as well. and the getDefinition is now there in case anybody ever needs it.

@travisbot

This pull request passes (merged 1b17705 into ba2b815).

@travisbot

This pull request passes (merged d0a07da into ba2b815).

@lsmith77
Owner

@pitpit before i merge this a quick question, is there a specific reason why you need this intermediate array rather than writing straight to $this->documentChangesets[$oid] ?

@pitpit

@lsmith77 yes, because I didn't want to interact with the loop https://github.com/pitpit/phpcr-odm/blob/d0a07da49a24422735f94384de79aeffadf0e72e/lib/Doctrine/ODM/PHPCR/UnitOfWork.php#L1387 in where the association changeset is builded

@travisbot

This pull request passes (merged db60354e into ba2b815).

@travisbot

This pull request passes (merged 6957703 into ba2b815).

@lsmith77 lsmith77 merged commit 059f3de into from
@lsmith77
Owner

thx .. merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
10 lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -1348,6 +1348,8 @@ private function executeInserts($documents)
}
);
+ $associationChangesets = array();
+
foreach ($oids as $oid => $id) {
$document = $documents[$oid];
$class = $this->dm->getClassMetadata(get_class($document));
@@ -1415,6 +1417,12 @@ private function executeInserts($documents)
}
} elseif (isset($class->associationsMappings[$fieldName])) {
$this->scheduledAssociationUpdates[$oid] = $document;
+
+ //populate $associationChangesets to force executeUpdates($this->scheduledAssociationUpdates)
+ //to only update association fields
+ $data = isset($associationChangesets[$oid]['fields']) ? $associationChangesets[$oid]['fields'] : array();
+ $data[$class->associationsMappings[$fieldName]['fieldName']] = $fieldValue;
+ $associationChangesets[$oid] = array('fields' => $data, 'reorderings' => array());
}
}
@@ -1427,6 +1435,8 @@ private function executeInserts($documents)
$this->evm->dispatchEvent(Event::postPersist, new LifecycleEventArgs($document, $this->dm));
}
}
+
+ $this->documentChangesets = array_merge($this->documentChangesets, $associationChangesets);
}
/**
View
127 tests/Doctrine/Tests/ODM/PHPCR/Functional/ProtectedPropertyTest.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Doctrine\Tests\ODM\PHPCR\Functional;
+
+use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
+
+/**
+ * @see http://www.doctrine-project.org/jira/browse/PHPCR-78
+ * @group functional
+ */
+class ProtectedPropertyTest extends \Doctrine\Tests\ODM\PHPCR\PHPCRFunctionalTestCase
+{
+ /**
+ * @var \Doctrine\ODM\PHPCR\DocumentManager
+ */
+ private $dm;
+
+ /**
+ * Class name of the document class
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @var \PHPCR\NodeInterface
+ */
+ private $node;
+
+ public function setUp()
+ {
+ $this->dm = $this->createDocumentManager();
+ $this->node = $this->resetFunctionalNode($this->dm);
+
+ $session = $this->dm->getPhpcrSession();
+ if (! $session instanceof \Jackalope\Session) {
+ $this->markTestSkipped('Not a Jackalope session');
+ }
+
+ $cnd = <<<CND
+<test='http://test.fr'>
+[test:protected_property_test] > nt:hierarchyNode
+ - reference (REFERENCE)
+CND;
+
+ $cnd2 = <<<CND
+<test='http://test.fr'>
+[test:protected_property_test2] > nt:hierarchyNode
+ - reference (REFERENCE)
+ - reference2 (REFERENCE)
+CND;
+
+ $ntm = $session->getWorkspace()->getNodeTypeManager();
+ $ntm->registerNodeTypesCnd($cnd, true);
+ $ntm->registerNodeTypesCnd($cnd2, true);
+ }
+
+ public function testPersistDocumentWithReferenceAndProtectedProperty()
+ {
+ $object = new ProtectedPropertyTestObj();
+ $object->id = '/functional/pp';
+
+ try {
+ $this->dm->persist($object);
+ $this->dm->flush();
+ $this->dm->clear();
+ } catch(\PHPCR\NodeType\ConstraintViolationException $e) {
+ $this->fail(sprintf('A ConstraintViolationException has been thrown when persisting document ("%s").', $e->getMessage()));
+ }
+
+ $this->assertTrue(true);
+ }
+
+ public function testPersistDocumentWithSeveralReferencesAndProtectedProperty()
+ {
+ $object = new ProtectedPropertyTestObj2();
+ $object->id = '/functional/pp';
+
+ try {
+ $this->dm->persist($object);
+ $this->dm->flush();
+ $this->dm->clear();
+ } catch(\PHPCR\NodeType\ConstraintViolationException $e) {
+ $this->fail(sprintf('A ConstraintViolationException has been thrown when persisting document ("%s").', $e->getMessage()));
+ }
+
+ $this->assertTrue(true);
+ }
+}
+
+/**
+ * @PHPCRODM\Document(nodeType="test:protected_property_test")
+ */
+class ProtectedPropertyTestObj
+{
+ /** @PHPCRODM\Id() */
+ public $id;
+
+ /** @PHPCRODM\ReferenceOne(strategy="hard") */
+ public $reference;
+
+ /** @PHPCRODM\Date(name="jcr:created") */
+ public $created;
+
+ /** @PHPCRODM\String(name="jcr:createdBy") */
+ public $createdBy;
+}
+
+/**
+ * @PHPCRODM\Document(nodeType="test:protected_property_test2")
+ */
+class ProtectedPropertyTestObj2
+{
+ /** @PHPCRODM\Id() */
+ public $id;
+
+ /** @PHPCRODM\ReferenceOne(strategy="hard") */
+ public $reference;
+
+ /** @PHPCRODM\ReferenceOne(strategy="hard") */
+ public $reference2;
+
+ /** @PHPCRODM\Date(name="jcr:created") */
+ public $created;
+
+ /** @PHPCRODM\String(name="jcr:createdBy") */
+ public $createdBy;
+}
Something went wrong with that request. Please try again.