Skip to content

Commit

Permalink
Support child restriction
Browse files Browse the repository at this point in the history
  • Loading branch information
dantleech committed May 30, 2016
1 parent 2e9765e commit 1e084bf
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 22 deletions.
6 changes: 4 additions & 2 deletions lib/Doctrine/ODM/PHPCR/DocumentClassMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
*/
class DocumentClassMapper implements DocumentClassMapperInterface
{
const CLASS_PROPERTY = 'phpcr:class';

private function expandClassName(DocumentManagerInterface $dm, $className = null)
{
if (null === $className) {
Expand All @@ -50,8 +52,8 @@ public function getClassName(DocumentManagerInterface $dm, NodeInterface $node,
{
$className = $this->expandClassName($dm, $className);

if ($node->hasProperty('phpcr:class')) {
$nodeClassName = $node->getProperty('phpcr:class')->getString();
if ($node->hasProperty(self::CLASS_PROPERTY)) {
$nodeClassName = $node->getProperty(self::CLASS_PROPERTY)->getString();

if (!empty($className)
&& $nodeClassName !== $className
Expand Down
5 changes: 5 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Annotations/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ class Document
* @var boolean
*/
public $uniqueNodeType;

/**
* @var array
*/
public $restrictChildren;
}
28 changes: 28 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,13 @@ class ClassMetadata implements ClassMetadataInterface
*/
public $parentClasses = array();

/**
* READ-ONLY: Child class restrictions.
*
* @var array
*/
public $childRestrictions = null;

/**
* The inherited fields of this class
*
Expand Down Expand Up @@ -1123,6 +1130,27 @@ public function getParentClasses()
return $this->parentClasses;
}

/**
* Return the allowed child classes.
*
* @return string[]
*/
public function getChildRestrictions()
{
return $this->childRestrictions;
}

/**
* Set the allowed child classes.
*
* @param string[] $childRestrictions
*/
public function setChildRestrictions(array $childRestrictions)
{
$this->childRestrictions = $childRestrictions;
}


/**
* Checks whether the class will generate an id via the repository.
*
Expand Down
4 changes: 4 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$metadata->setTranslator($documentAnnot->translator);
}

if (null !== $documentAnnot->restrictChildren) {
$metadata->setChildRestrictions($documentAnnot->restrictChildren);
}

foreach ($reflClass->getProperties() as $property) {
if ($metadata->isInheritedField($property->name)
&& $metadata->name !== $property->getDeclaringClass()->getName()
Expand Down
1 change: 0 additions & 1 deletion lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
*/
class ReferenceManyCollection extends PersistentCollection
{

const REFERENCE_TYPE_PATH = 'path';
const REFERENCE_TYPE_UUID = 'uuid';

Expand Down
53 changes: 49 additions & 4 deletions lib/Doctrine/ODM/PHPCR/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use PHPCR\Util\PathHelper;
use PHPCR\Util\NodeHelper;
use Jackalope\Session as JackalopeSession;
use Doctrine\ODM\PHPCR\Exception\OutOfBoundsException;

/**
* Unit of work class
Expand Down Expand Up @@ -2299,17 +2300,17 @@ private function executeInserts($documents)

$order = array_flip(array_values($oids));
uasort($oids, function ($a, $b) use ($order) {
// compute the node depths
// compute the node depths
$aCount = substr_count($a, '/');
$bCount = substr_count($b, '/');
$bCount = substr_count($b, '/');

// ensure that the original order is maintained for nodes with the same depth
if ($aCount === $bCount) {
return ($order[$a] < $order[$b]) ? -1 : 1;
}

return ($aCount < $bCount) ? -1 : 1;
}
return ($aCount < $bCount) ? -1 : 1;
}
);

$associationChangesets = $associationUpdates = array();
Expand All @@ -2332,6 +2333,8 @@ private function executeInserts($documents)
}

$parentNode = $this->session->getNode(PathHelper::getParentPath($id));
$this->validateChildRestrictions($parentNode, $class->name);

$nodename = PathHelper::getNodeName($id);
$node = $parentNode->addNode($nodename, $class->nodeType);
if ($class->node) {
Expand Down Expand Up @@ -2731,6 +2734,9 @@ private function executeMoves($documents)
);
}

$parentNode = $this->session->getNode(PathHelper::getParentPath($targetPath));
$this->validateChildRestrictions($parentNode, $class->name);

$this->session->move($sourcePath, $targetPath);

// update fields nodename, parentMapping and depth if they exist in this type
Expand Down Expand Up @@ -3968,4 +3974,43 @@ public function invokeGlobalEvent($eventName, EventArgs $event)
$this->eventManager->dispatchEvent($eventName, $event);
}
}

/**
* If the parent node has child restrictions, ensure that the given
* class name is within them.
*
* @param NodeInterface $parentNode
* @param string $classFqn
*/
private function validateChildRestrictions(NodeInterface $parentNode, $classFqn)
{
$parentClass = $this->documentClassMapper->getClassName($this->dm, $parentNode);

if (null === $parentClass) {
return;
}

$metadata = $this->dm->getClassMetadata($parentClass);
$childRestrictions = $metadata->getChildRestrictions();

if (null === $childRestrictions) {
return;
}

if (array() === $childRestrictions) {
throw new OutOfBoundsException(sprintf(
'Document "%s" cannot have children',
$parentClass
));
}

if (!in_array($classFqn, $childRestrictions)) {
throw new OutOfBoundsException(sprintf(
'Document "%s" does not allow children of type "%s". Allowed child classes "%s"',
$metadata->name,
$classFqn,
implode('", "', $childRestrictions)
));
}
}
}
17 changes: 17 additions & 0 deletions tests/Doctrine/Tests/Models/CMS/CmsArticleFolder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Doctrine\Tests\Models\CMS;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;

/**
* @PHPCRODM\Document(restrictChildren={"Doctrine\Tests\Models\CMS\CmsArticle"})
*/
class CmsArticleFolder
{
/**
* @PHPCRODM\Id
*/
public $id;
}
12 changes: 6 additions & 6 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/CascadeRemoveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ public function setUp()
public function testCascadeRemoveBidirectionalFromOwningSide()
{
$this->wrapRemove(function ($dm, $user, $group1, $group2) {
$dm->remove($user);
$dm->flush();
});
$dm->remove($user);
$dm->flush();
});
}

public function testCascadeRemoveFromInverseSide()
{
$this->wrapRemove(function ($dm, $user, $group1, $group2) {
$dm->remove($group1);
$dm->flush();
});
$dm->remove($group1);
$dm->flush();
});
}

public function wrapRemove($closure)
Expand Down
57 changes: 57 additions & 0 deletions tests/Doctrine/Tests/ODM/PHPCR/Functional/UnitOfWorkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Doctrine\Tests\Models\References\ParentNoNodeNameTestObj;
use Doctrine\Tests\Models\References\ParentTestObj;
use Doctrine\Tests\Models\Translation\Comment;
use Doctrine\Tests\Models\CMS\CmsArticleFolder;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsArticle;

/**
* @group functional
Expand Down Expand Up @@ -185,4 +188,58 @@ public function testFetchingMultipleHierarchicalObjectsWithChildIdFirst()
$this->assertSame($child->parent, $parent);
$this->assertSame('parent', $parent->nodename);
}

/**
* @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
* @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsArticleFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsGroup". Allowed child classes "Doctrine\Tests\Models\CMS\CmsArticle"
*/
public function testRestrictChildrenInvalidChildren()
{
$articleFolder = new CmsArticleFolder();
$articleFolder->id = '/functional/articles';

$article = new CmsGroup();
$article->id = '/functional/articles/address';
$article->name = 'invalid-child';

$this->dm->persist($articleFolder);
$this->dm->persist($article);
$this->dm->flush();
}

public function testRestrictChildrenValidChildren()
{
$articleFolder = new CmsArticleFolder();
$articleFolder->id = '/functional/articles';

$article = new CmsArticle();
$article->id = '/functional/articles/article';
$article->topic = 'greetings';
$article->text = 'Hello World';

$this->dm->persist($articleFolder);
$this->dm->persist($article);
$this->dm->flush();
}

/**
* @expectedException Doctrine\ODM\PHPCR\Exception\OutOfBoundsException
* @expectedExceptionMessage Document "Doctrine\Tests\Models\CMS\CmsArticleFolder" does not allow children of type "Doctrine\Tests\Models\CMS\CmsGroup". Allowed child classes "Doctrine\Tests\Models\CMS\CmsArticle"
*/
public function testRestrictChildrenInvalidUpdate()
{
$articleFolder = new CmsArticleFolder();
$articleFolder->id = '/functional/articles';

$article = new CmsGroup();
$article->id = '/functional/address';
$article->name = 'invalid-child';

$this->dm->persist($articleFolder);
$this->dm->persist($article);
$this->dm->flush();

$this->dm->move($article, '/functional/articles/address');
$this->dm->flush();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public function setUp()
$mdf->expects($this->any())
->method('getMetadataFor')
->will($this->returnCallback(function ($documentFqn) use ($me) {

$meta = $me->getMockBuilder(
'Doctrine\ODM\PHPCR\Mapping\ClassMetadata'
)->disableOriginalConstructor()->getMock();
Expand All @@ -46,7 +45,6 @@ public function setUp()
$meta->expects($me->any())
->method('getFieldMapping')
->will($me->returnCallback(function ($name) {

$res = array(
'fieldName' => $name,
'property' => $name.'_phpcr',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ public function testGetLocalesFor()
$node->getProperties('test:*')->willReturn($properties);

$locales = $this->strategy->getLocalesFor($document, $node->reveal(), $classMetadata->reveal());
$expected = array_values(array_filter($localizedPropNames, function ($valid) { return $valid; }));
$expected = array_values(array_filter($localizedPropNames, function ($valid) {
return $valid;
}));
$this->assertEquals($expected, $locales);
}
}
29 changes: 23 additions & 6 deletions tests/Doctrine/Tests/ODM/PHPCR/UnitOfWorkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,34 @@
*/
class UnitOfWorkTest extends PHPCRTestCase
{
/** @var DocumentManager */
/**
* @var DocumentManager
*/
private $dm;
/** @var UnitOfWork */

/**
* @var UnitOfWork
*/
private $uow;
/** @var Factory */

/**
* @var Factory
*/
private $factory;
/** @var \PHPCR\SessionInterface */

/**
* @var \PHPCR\SessionInterface
*/
private $session;
/** @var \Jackalope\ObjectManager */

/**
* @var \Jackalope\ObjectManager
*/
private $objectManager;
/** @var string */

/**
* @var string
*/
private $type;

public function setUp()
Expand Down

0 comments on commit 1e084bf

Please sign in to comment.