-
-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4355 from mficzel/feature/flowQueryRemoveAndUniqu…
…eForNodes 9.0 FEATURE: Add flowQuery operation `unique` and `remove` for Nodes
- Loading branch information
Showing
5 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/CreateNodeHashTrait.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Neos\ContentRepository\NodeAccess\FlowQueryOperations; | ||
|
||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
|
||
trait CreateNodeHashTrait | ||
{ | ||
/** | ||
* Create a string hash containing the nodeAggregateId, cr-id, contentStream->id, dimensionSpacePoint->hash | ||
* and visibilityConstraints->hash. To be used for ensuring uniqueness or removing nodes. | ||
* | ||
* @see Node::equals() for comparison | ||
*/ | ||
protected function createNodeHash(Node $node): string | ||
{ | ||
return md5( | ||
implode( | ||
':', | ||
[ | ||
$node->nodeAggregateId->value, | ||
$node->subgraphIdentity->contentRepositoryId->value, | ||
$node->subgraphIdentity->contentStreamId->value, | ||
$node->subgraphIdentity->dimensionSpacePoint->hash, | ||
$node->subgraphIdentity->visibilityConstraints->getHash() | ||
] | ||
) | ||
); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/RemoveOperation.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
namespace Neos\ContentRepository\NodeAccess\FlowQueryOperations; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindReferencesFilter; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; | ||
use Neos\Eel\FlowQuery\FlowQuery; | ||
use Neos\Eel\FlowQuery\OperationInterface; | ||
use Neos\Flow\Annotations as Flow; | ||
|
||
/** | ||
* Removes the given Node from the current context. | ||
* | ||
* The operation accepts one argument that may be an Array, a FlowQuery | ||
* or an Object. | ||
* | ||
* !!! This is a Node specific implementation of the generic `remove` operation!!! | ||
* | ||
* The result is an array of {@see Node} instances. | ||
* | ||
* @api To be used in Fusion, in php this should be implemented directly | ||
*/ | ||
final class RemoveOperation implements OperationInterface | ||
{ | ||
use CreateNodeHashTrait; | ||
|
||
public function canEvaluate($context): bool | ||
{ | ||
return count($context) === 0 || (isset($context[0]) && ($context[0] instanceof Node)); | ||
} | ||
|
||
public function evaluate(FlowQuery $flowQuery, array $arguments): void | ||
{ | ||
$nodeHashesToRemove = []; | ||
if (isset($arguments[0])) { | ||
if (is_iterable($arguments[0])) { | ||
/** @var Node $node */ | ||
foreach ($arguments[0] as $node) { | ||
$nodeHashesToRemove[] = $this->createNodeHash($node); | ||
} | ||
} elseif ($arguments[0] instanceof Node) { | ||
$nodeHashesToRemove[] = $this->createNodeHash($arguments[0]); | ||
} | ||
} | ||
|
||
$filteredContext = []; | ||
foreach ($flowQuery->getContext() as $node) { | ||
$hash = $this->createNodeHash($node); | ||
if (!in_array($hash, $nodeHashesToRemove, true)) { | ||
$filteredContext[] = $node; | ||
} | ||
} | ||
|
||
$flowQuery->setContext($filteredContext); | ||
} | ||
|
||
public static function getShortName(): string | ||
{ | ||
return 'remove'; | ||
} | ||
|
||
public static function getPriority(): int | ||
{ | ||
return 100; | ||
} | ||
|
||
public static function isFinal(): bool | ||
{ | ||
return false; | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/UniqueOperation.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<?php | ||
namespace Neos\ContentRepository\NodeAccess\FlowQueryOperations; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindReferencesFilter; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; | ||
use Neos\Eel\FlowQuery\FlowQuery; | ||
use Neos\Eel\FlowQuery\OperationInterface; | ||
use Neos\Flow\Annotations as Flow; | ||
|
||
/** | ||
* "unique" operation working on Nodes | ||
* | ||
* This operation can be used to ensure that nodes are only once in the flow query context | ||
* | ||
* ${q(node).backReferences().nodes().unique()get()} | ||
* | ||
* The result is an array of {@see Node} instances. | ||
* | ||
* !!! This is a Node specific implementation of the generic `unique` operation!!! | ||
* | ||
* @api To be used in Fusion, in php this should be implemented directly | ||
*/ | ||
final class UniqueOperation implements OperationInterface | ||
{ | ||
use CreateNodeHashTrait; | ||
|
||
public function canEvaluate($context): bool | ||
{ | ||
return count($context) === 0 || (isset($context[0]) && ($context[0] instanceof Node)); | ||
} | ||
|
||
public function evaluate(FlowQuery $flowQuery, array $arguments): void | ||
{ | ||
$nodesByHash = []; | ||
/** @var Node $contextNode */ | ||
foreach ($flowQuery->getContext() as $node) { | ||
$hash = $this->createNodeHash($node); | ||
if (!array_key_exists($hash, $nodesByHash)) { | ||
$nodesByHash[$hash] = $node; | ||
} | ||
} | ||
$flowQuery->setContext(array_values($nodesByHash)); | ||
} | ||
|
||
public static function getShortName(): string | ||
{ | ||
return 'unique'; | ||
} | ||
|
||
public static function getPriority(): int | ||
{ | ||
return 100; | ||
} | ||
|
||
public static function isFinal(): bool | ||
{ | ||
return false; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/RemoveOperationTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
<?php | ||
namespace Neos\ContentRepository\NodeAccess\Tests\Unit\FlowQueryOperations; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\Eel\FlowQuery\FlowQuery; | ||
use Neos\ContentRepository\NodeAccess\FlowQueryOperations\RemoveOperation; | ||
|
||
/** | ||
* Testcase for the FlowQuery RemoveOperation | ||
*/ | ||
class RemoveOperationTest extends AbstractQueryOperationsTest | ||
{ | ||
|
||
public function setUp(): void | ||
{ | ||
$this->markTestSkipped('fix and re-enable for Neos 9.0'); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function canBeAppliedOnceNodesAreInContextOrContextIsEmpty() | ||
{ | ||
$node = $this->mockNode('nudelsuppe'); | ||
$operation = new RemoveOperation(); | ||
self::assertTrue($operation->canEvaluate([])); | ||
self::assertTrue($operation->canEvaluate([$node])); | ||
self::assertFalse($operation->canEvaluate([123])); | ||
self::assertFalse($operation->canEvaluate(["string"])); | ||
self::assertFalse($operation->canEvaluate([new \stdClass()])); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function removeWillRemoveTheNodeGivenAsSingleArgument() | ||
{ | ||
$node1 = $this->mockNode('nudelsuppe'); | ||
$node2 = $this->mockNode('tomatensuppe'); | ||
|
||
$nodeToRemove = $this->mockNode('tomatensuppe'); | ||
|
||
$flowQuery = new FlowQuery([$node1, $node2]); | ||
|
||
$operation = new RemoveOperation(); | ||
$operation->evaluate($flowQuery, [$nodeToRemove]); | ||
|
||
$output = $flowQuery->getContext(); | ||
self::assertContains($node1, $output); | ||
self::assertNotContains($node2, $output); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function removeWillRemoveTheNodeGivenAsArrayArgument() | ||
{ | ||
$node1 = $this->mockNode('nudelsuppe'); | ||
$node2 = $this->mockNode('tomatensuppe'); | ||
|
||
$nodeToRemove = $this->mockNode('tomatensuppe'); | ||
|
||
$q = new FlowQuery([$node1, $node2]); | ||
|
||
$operation = new RemoveOperation(); | ||
$operation->evaluate($q, [[$nodeToRemove]]); | ||
|
||
$output = $q->getContext(); | ||
self::assertContains($node1, $output); | ||
self::assertNotContains($node2, $output); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function removeWillRemoveTheNodeGivenAsFlowQueryArgument() | ||
{ | ||
$node1 = $this->mockNode('nudelsuppe'); | ||
$node2 = $this->mockNode('tomatensuppe'); | ||
|
||
$flowQueryToRemove = new FlowQuery($this->mockNode('tomatensuppe')); | ||
|
||
$q = new FlowQuery([$node1, $node2]); | ||
|
||
$operation = new RemoveOperation(); | ||
$operation->evaluate($q, [$flowQueryToRemove]); | ||
|
||
$output = $q->getContext(); | ||
self::assertContains($node1, $output); | ||
self::assertNotContains($node2, $output); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
Neos.ContentRepository.NodeAccess/Tests/Unit/FlowQueryOperations/UniqueOperationTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
namespace Neos\ContentRepository\NodeAccess\Tests\Unit\FlowQueryOperations; | ||
|
||
/* | ||
* This file is part of the Neos.ContentRepository package. | ||
* | ||
* (c) Contributors of the Neos Project - www.neos.io | ||
* | ||
* This package is Open Source Software. For the full copyright and license | ||
* information, please view the LICENSE file which was distributed with this | ||
* source code. | ||
*/ | ||
|
||
use Neos\ContentRepository\NodeAccess\FlowQueryOperations\UniqueOperation; | ||
use Neos\Eel\FlowQuery\FlowQuery; | ||
|
||
/** | ||
* Testcase for the FlowQuery UniqueOperation | ||
*/ | ||
class UniqueOperationTest extends AbstractQueryOperationsTest | ||
{ | ||
|
||
public function setUp(): void | ||
{ | ||
$this->markTestSkipped('fix and re-enable for Neos 9.0'); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function canBeAppliedOnceNodesAreInContextOrContextIsEmpty() | ||
{ | ||
$node = $this->mockNode('nudelsuppe'); | ||
$operation = new UniqueOperation(); | ||
self::assertTrue($operation->canEvaluate([])); | ||
self::assertTrue($operation->canEvaluate([$node])); | ||
self::assertFalse($operation->canEvaluate([123])); | ||
self::assertFalse($operation->canEvaluate(["string"])); | ||
self::assertFalse($operation->canEvaluate([new \stdClass()])); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function willRemoveDuplicateEntriesWithTheSameNodeAggregateId() | ||
{ | ||
$node1 = $this->mockNode('nudelsuppe'); | ||
$node2 = $this->mockNode('tomatensuppe'); | ||
$node3 = $this->mockNode('nudelsuppe'); | ||
$node4 = $this->mockNode('bohnensuppe'); | ||
|
||
$flowQuery = new FlowQuery([$node1, $node2, $node3, $node4, $node1, $node2]); | ||
|
||
$operation = new UniqueOperation(); | ||
$operation->evaluate($flowQuery, []); | ||
|
||
$output = $flowQuery->getContext(); | ||
self::assertSame([$node1, $node2, $node4], $output); | ||
} | ||
} |