-
-
Notifications
You must be signed in to change notification settings - Fork 224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FEATURE: Add findByCriteria
and findByIdentifier
flowQuery operation
#5435
Draft
mficzel
wants to merge
1
commit into
neos:9.0
Choose a base branch
from
mficzel:feature/findByFlowQueryOperations
base: 9.0
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindByCriteriaOperation.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,170 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
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\Flow\Annotations as Flow; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\FindDescendantNodesFilter; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\NodeType\NodeTypeCriteria; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\Pagination\Pagination; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Filter\PropertyValue\PropertyValueCriteriaParser; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; | ||
use Neos\Eel\FlowQuery\FlowQuery; | ||
use Neos\Eel\FlowQuery\FlowQueryException; | ||
use Neos\Eel\FlowQuery\Operations\AbstractOperation; | ||
|
||
/** | ||
* "findByCriteria" operation working on ContentRepository nodes. This operation allows for retrieval of descendant nodes | ||
* | ||
* Argument 1 (string|null): nodeTypeFilter, A list of NodeType Names seperated by ",", disallowed NodeTypes are prefixed with "!" | ||
* | ||
* Argument 2 (string|null): propertyValueCriteria A property criteria in the form | ||
* | ||
* property criteria are specified as "<property> <operator> <value>". Multiple criteria can be combined using "AND", "OR", "NOT" and "()" | ||
* | ||
* | ||
* property criteria support the following comparison operators: | ||
* | ||
* =~ : Strict equality of case-insensitive value and operand | ||
* = : Strict equality of value and operand | ||
* !=~ : Strict inequality of case-insensitive value and operand | ||
* != : Strict inequality of value and operand | ||
* < : Value is less than operand | ||
* <= : Value is less than or equal to operand | ||
* > : Value is greater than operand | ||
* >= : Value is greater than or equal to operand | ||
* $=~ : Value ends with operand (string-based) or case-insensitive value's last element is equal to operand (array-based) | ||
* $= : Value ends with operand (string-based) or value's last element is equal to operand (array-based) | ||
* ^=~ : Value starts with operand (string-based) or case-insensitive value's first element is equal to operand (array-based) | ||
* ^= : Value starts with operand (string-based) or value's first element is equal to operand (array-based) | ||
* *=~ : Value contains operand (string-based) or case-insensitive value contains an element that is equal to operand (array based) | ||
* *= : Value contains operand (string-based) or value contains an element that is equal to operand (array based) | ||
* | ||
* criteria can be combined using "AND" and "OR": | ||
* | ||
* "prop1 ^= 'foo' AND (prop2 = 'bar' OR prop3 = 'baz')" | ||
* | ||
* furthermore "NOT" can be used to negate a whole sub query | ||
* | ||
* "prop1 ^= 'foo' AND NOT (prop2 = 'bar' OR prop3 = 'baz')" | ||
* | ||
* Argument 3 ({limit?:int, offset?:int}}): Pagination of the date | ||
* | ||
* | ||
* Example (node type): | ||
* | ||
* q(node).findByCriteria('Neos.NodeTypes:Text') | ||
* | ||
* Example (multiple node types): | ||
* | ||
* q(node).findByCriteria('Neos.NodeTypes:Text,Neos.NodeTypes:Image') | ||
* | ||
* Example (node type with property filter): | ||
* | ||
* q(node).findByCriteria('Neos.NodeTypes:Text', 'text*="Neos"') | ||
* | ||
* Example (node type with property filter and pagination): | ||
* | ||
* q(node).findByCriteria('Neos.NodeTypes:Document', 'title*="Flow"', {limit:10, offset:2}) | ||
*/ | ||
class FindByCriteriaOperation extends AbstractOperation | ||
{ | ||
use CreateNodeHashTrait; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @var string | ||
*/ | ||
protected static $shortName = 'findByCriteria'; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @var integer | ||
*/ | ||
protected static $priority = 100; | ||
|
||
/** | ||
* @Flow\Inject | ||
* @var ContentRepositoryRegistry | ||
*/ | ||
protected $contentRepositoryRegistry; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @param array<int,mixed> $context (or array-like object) onto which this operation should be applied | ||
* @return boolean true if the operation can be applied onto the $context, false otherwise | ||
*/ | ||
public function canEvaluate($context) | ||
{ | ||
foreach ($context as $contextNode) { | ||
if (!$contextNode instanceof Node) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
/** | ||
* This operation operates rather on the given Context object than on the given node | ||
* and thus may work with the legacy node interface until subgraphs are available | ||
* {@inheritdoc} | ||
* | ||
* @param FlowQuery<int,mixed> $flowQuery the FlowQuery object | ||
* @param array<int,mixed> $arguments the arguments for this operation | ||
* @throws FlowQueryException | ||
* @throws \Neos\Eel\Exception | ||
* @throws \Neos\Eel\FlowQuery\FizzleException | ||
*/ | ||
public function evaluate(FlowQuery $flowQuery, array $arguments): void | ||
{ | ||
/** @var array<int,Node> $contextNodes */ | ||
$contextNodes = $flowQuery->getContext(); | ||
if (count($contextNodes) === 0) { | ||
return; | ||
} | ||
|
||
$firstContextNode = reset($contextNodes); | ||
assert($firstContextNode instanceof Node); | ||
|
||
$nodeTypeFilter = $arguments[0] ?? null; | ||
$propertyValueFilter = $arguments[1] ?? null; | ||
$pagination = $arguments[2] ?? null; | ||
|
||
assert($nodeTypeFilter === null || is_string($nodeTypeFilter)); | ||
assert($propertyValueFilter === null || is_string($propertyValueFilter)); | ||
assert($pagination === null || is_array($pagination)); | ||
|
||
/** @var Node[] $result */ | ||
$result = []; | ||
$findDescendentNodesFilter = FindDescendantNodesFilter::create( | ||
nodeTypes: $nodeTypeFilter ? NodeTypeCriteria::fromFilterString($nodeTypeFilter) : null, | ||
propertyValue: $propertyValueFilter ? PropertyValueCriteriaParser::parse($propertyValueFilter) : null, | ||
pagination: $pagination ? Pagination::fromArray($pagination) : null | ||
); | ||
|
||
/** @var Node $contextNode */ | ||
foreach ($flowQuery->getContext() as $contextNode) { | ||
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode); | ||
foreach ($subgraph->findDescendantNodes($contextNode->aggregateId, $findDescendentNodesFilter) as $descendant) { | ||
$result[] = $descendant; | ||
} | ||
} | ||
|
||
$flowQuery->setContext($result); | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
Neos.ContentRepository.NodeAccess/Classes/FlowQueryOperations/FindByIdentifierOperation.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,111 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
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\Flow\Annotations as Flow; | ||
use Neos\ContentRepository\Core\Projection\ContentGraph\Node; | ||
use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; | ||
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry; | ||
use Neos\Eel\FlowQuery\FlowQuery; | ||
use Neos\Eel\FlowQuery\FlowQueryException; | ||
use Neos\Eel\FlowQuery\Operations\AbstractOperation; | ||
|
||
/** | ||
* "findByIdentifier" operation working on ContentRepository nodes. This operation allows for retrieval of nodes by identifier | ||
* from the current subgraph | ||
* | ||
* Example: | ||
* | ||
* q(site).findByIdentifier('30e893c1-caef-0ca5-b53d-e5699bb8e506') | ||
*/ | ||
class FindByIdentifierOperation extends AbstractOperation | ||
{ | ||
use CreateNodeHashTrait; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @var string | ||
*/ | ||
protected static $shortName = 'findByIdentifier'; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @var integer | ||
*/ | ||
protected static $priority = 100; | ||
|
||
/** | ||
* @Flow\Inject | ||
* @var ContentRepositoryRegistry | ||
*/ | ||
protected $contentRepositoryRegistry; | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @param array<int,mixed> $context (or array-like object) onto which this operation should be applied | ||
* @return boolean true if the operation can be applied onto the $context, false otherwise | ||
*/ | ||
public function canEvaluate($context) | ||
{ | ||
foreach ($context as $contextNode) { | ||
if (!$contextNode instanceof Node) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
/** | ||
* This operation operates rather on the given Context object than on the given node | ||
* and thus may work with the legacy node interface until subgraphs are available | ||
* {@inheritdoc} | ||
* | ||
* @param FlowQuery<int,mixed> $flowQuery the FlowQuery object | ||
* @param array<int,mixed> $arguments the arguments for this operation | ||
* @throws FlowQueryException | ||
* @throws \Neos\Eel\Exception | ||
* @throws \Neos\Eel\FlowQuery\FizzleException | ||
*/ | ||
public function evaluate(FlowQuery $flowQuery, array $arguments): void | ||
{ | ||
/** @var array<int,Node> $contextNodes */ | ||
$contextNodes = $flowQuery->getContext(); | ||
if (count($contextNodes) === 0 || empty($arguments[0])) { | ||
return; | ||
} | ||
|
||
$firstContextNode = reset($contextNodes); | ||
assert($firstContextNode instanceof Node); | ||
|
||
$nodeAggregateId = NodeAggregateId::fromString($arguments[0]); | ||
|
||
/** @var Node[] $result */ | ||
$result = []; | ||
|
||
/** @var Node $contextNode */ | ||
foreach ($flowQuery->getContext() as $contextNode) { | ||
$subgraph = $this->contentRepositoryRegistry->subgraphForNode($contextNode); | ||
$nodeByIdentifier = $subgraph->findNodeById($nodeAggregateId); | ||
if ($nodeByIdentifier) { | ||
$result[] = $nodeByIdentifier; | ||
} | ||
} | ||
|
||
$flowQuery->setContext($result); | ||
} | ||
} |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe in this "next" generation of flowquery we want to forbid the functionality that we iterate over all context nodes and either throw if there are multiple nodes or just always pick the first one. Operating on multiple nodes might not give the expected ordering as one might think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one thing that i dislike most is that context can contain anything. Would really like to limit that to nodes