Skip to content

Commit

Permalink
Merge pull request #4355 from mficzel/feature/flowQueryRemoveAndUniqu…
Browse files Browse the repository at this point in the history
…eForNodes

9.0 FEATURE: Add flowQuery operation `unique` and `remove` for Nodes
  • Loading branch information
skurfuerst authored Sep 1, 2023
2 parents 444ca6f + 65fea07 commit 37bf63e
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 0 deletions.
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()
]
)
);
}
}
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;
}
}
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;
}
}
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);
}
}
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);
}
}

0 comments on commit 37bf63e

Please sign in to comment.