12
12
*/
13
13
14
14
use Neos \ContentRepository \Core \Projection \ContentGraph \Filter \FindChildNodesFilter ;
15
+ use Neos \ContentRepository \Core \Projection \ContentGraph \Filter \FindDescendantNodesFilter ;
16
+ use Neos \ContentRepository \Core \Projection \ContentGraph \Nodes ;
15
17
use Neos \ContentRepository \Core \SharedModel \Node \NodeName ;
16
18
use Neos \ContentRepository \Core \Projection \ContentGraph \Filter \NodeType \NodeTypeCriteria ;
17
19
use Neos \ContentRepository \Core \NodeType \NodeTypeNames ;
20
+ use Neos \ContentRepository \NodeAccess \Filter \NodeFilterCriteriaGroup ;
21
+ use Neos \ContentRepository \NodeAccess \Filter \NodeFilterCriteriaGroupFactory ;
18
22
use Neos \ContentRepositoryRegistry \ContentRepositoryRegistry ;
19
23
use Neos \Eel \FlowQuery \FizzleParser ;
20
24
use Neos \Eel \FlowQuery \FlowQuery ;
@@ -73,15 +77,28 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
73
77
{
74
78
$ output = [];
75
79
$ outputNodeAggregateIds = [];
80
+ $ contextNodes = $ flowQuery ->getContext ();
81
+
76
82
if (isset ($ arguments [0 ]) && !empty ($ arguments [0 ])) {
77
- $ parsedFilter = FizzleParser::parseFilterGroup ($ arguments [0 ]);
78
- if ($ this ->earlyOptimizationOfFilters ($ flowQuery , $ parsedFilter )) {
83
+ // optimized cr query for instanceof and attribute filters
84
+ $ nodeFilterCriteriaGroup = NodeFilterCriteriaGroupFactory::createFromFizzleExpressionString ($ arguments [0 ]);
85
+ if ($ nodeFilterCriteriaGroup instanceof NodeFilterCriteriaGroup) {
86
+ $ result = Nodes::createEmpty ();
87
+ foreach ($ nodeFilterCriteriaGroup as $ nodeFilterCriteria ) {
88
+ $ findChildNodesFilter = FindChildNodesFilter::create (nodeTypes: $ nodeFilterCriteria ->nodeTypeCriteria , propertyValue: $ nodeFilterCriteria ->propertyValueCriteria );
89
+ foreach ($ contextNodes as $ contextNode ) {
90
+ $ subgraph = $ this ->contentRepositoryRegistry ->subgraphForNode ($ contextNode );
91
+ $ descendantNodes = $ subgraph ->findChildNodes ($ contextNode ->nodeAggregateId , $ findChildNodesFilter );
92
+ $ result = $ result ->merge ($ descendantNodes );
93
+ }
94
+ }
95
+ $ flowQuery ->setContext (iterator_to_array ($ result ->getIterator ()));
79
96
return ;
80
97
}
81
98
}
82
99
83
100
/** @var Node $contextNode */
84
- foreach ($ flowQuery -> getContext () as $ contextNode ) {
101
+ foreach ($ contextNodes as $ contextNode ) {
85
102
$ childNodes = $ this ->contentRepositoryRegistry ->subgraphForNode ($ contextNode )
86
103
->findChildNodes ($ contextNode ->nodeAggregateId , FindChildNodesFilter::create ());
87
104
foreach ($ childNodes as $ childNode ) {
@@ -97,122 +114,4 @@ public function evaluate(FlowQuery $flowQuery, array $arguments)
97
114
$ flowQuery ->pushOperation ('filter ' , $ arguments );
98
115
}
99
116
}
100
-
101
- /**
102
- * Optimize for typical use cases, filter by node name and filter
103
- * by NodeType (instanceof). These cases are now optimized and will
104
- * only load the nodes that match the filters.
105
- *
106
- * @param FlowQuery<int,mixed> $flowQuery
107
- * @param array<string,mixed> $parsedFilter
108
- * @return boolean
109
- * @throws \Neos\Eel\Exception
110
- */
111
- protected function earlyOptimizationOfFilters (FlowQuery $ flowQuery , array $ parsedFilter )
112
- {
113
- $ optimized = false ;
114
- $ output = [];
115
- $ outputNodeAggregateIds = [];
116
- foreach ($ parsedFilter ['Filters ' ] as $ filter ) {
117
- $ instanceOfFilters = [];
118
- $ attributeFilters = [];
119
- if (isset ($ filter ['AttributeFilters ' ])) {
120
- foreach ($ filter ['AttributeFilters ' ] as $ attributeFilter ) {
121
- if ($ attributeFilter ['Operator ' ] === 'instanceof ' && $ attributeFilter ['Identifier ' ] === null ) {
122
- $ instanceOfFilters [] = $ attributeFilter ;
123
- } else {
124
- $ attributeFilters [] = $ attributeFilter ;
125
- }
126
- }
127
- }
128
-
129
- // Only apply optimization if there's a property name filter or an instanceof filter
130
- // or another filter already did optimization
131
- if ((isset ($ filter ['PropertyNameFilter ' ]) || isset ($ filter ['PathFilter ' ]))
132
- || count ($ instanceOfFilters ) > 0 || $ optimized === true ) {
133
- $ optimized = true ;
134
- $ filteredOutput = [];
135
- $ filteredOutputNodeIdentifiers = [];
136
- // Optimize property name filter if present
137
- if (isset ($ filter ['PropertyNameFilter ' ]) || isset ($ filter ['PathFilter ' ])) {
138
- $ nodePath = $ filter ['PropertyNameFilter ' ] ?? $ filter ['PathFilter ' ];
139
- $ nodePathSegments = explode ('/ ' , $ nodePath );
140
- /** @var Node $contextNode */
141
- foreach ($ flowQuery ->getContext () as $ contextNode ) {
142
- $ currentPathSegments = $ nodePathSegments ;
143
- $ resolvedNode = $ contextNode ;
144
- while (($ nodePathSegment = array_shift ($ currentPathSegments )) && !is_null ($ resolvedNode )) {
145
- $ resolvedNode = $ this ->contentRepositoryRegistry ->subgraphForNode ($ resolvedNode )
146
- ->findChildNodeConnectedThroughEdgeName (
147
- $ resolvedNode ->nodeAggregateId ,
148
- NodeName::fromString ($ nodePathSegment )
149
- );
150
- }
151
-
152
- if (!is_null ($ resolvedNode ) && !isset ($ filteredOutputNodeIdentifiers [
153
- $ resolvedNode ->nodeAggregateId ->value
154
- ])) {
155
- $ filteredOutput [] = $ resolvedNode ;
156
- $ filteredOutputNodeIdentifiers [$ resolvedNode ->nodeAggregateId ->value ] = true ;
157
- }
158
- }
159
- } elseif (count ($ instanceOfFilters ) > 0 ) {
160
- // Optimize node type filter if present
161
- $ allowedNodeTypes = array_map (function ($ instanceOfFilter ) {
162
- return $ instanceOfFilter ['Operand ' ];
163
- }, $ instanceOfFilters );
164
- /** @var Node $contextNode */
165
- foreach ($ flowQuery ->getContext () as $ contextNode ) {
166
- $ childNodes = $ this ->contentRepositoryRegistry ->subgraphForNode ($ contextNode )
167
- ->findChildNodes (
168
- $ contextNode ->nodeAggregateId ,
169
- FindChildNodesFilter::create (
170
- nodeTypes: NodeTypeCriteria::create (
171
- NodeTypeNames::fromStringArray ($ allowedNodeTypes ),
172
- NodeTypeNames::createEmpty ()
173
- )
174
- )
175
- );
176
-
177
- foreach ($ childNodes as $ childNode ) {
178
- if (!isset ($ filteredOutputNodeIdentifiers [
179
- $ childNode ->nodeAggregateId ->value
180
- ])) {
181
- $ filteredOutput [] = $ childNode ;
182
- $ filteredOutputNodeIdentifiers [$ childNode ->nodeAggregateId ->value ] = true ;
183
- }
184
- }
185
- }
186
- }
187
-
188
- // Apply attribute filters if present
189
- if (isset ($ filter ['AttributeFilters ' ])) {
190
- $ attributeFilters = array_reduce ($ filter ['AttributeFilters ' ], function (
191
- $ filters ,
192
- $ attributeFilter
193
- ) {
194
- return $ filters . $ attributeFilter ['text ' ];
195
- });
196
- $ filteredFlowQuery = new FlowQuery ($ filteredOutput );
197
- $ filteredFlowQuery ->pushOperation ('filter ' , [$ attributeFilters ]);
198
- $ filteredOutput = $ filteredFlowQuery ->getContext ();
199
- }
200
-
201
- // Add filtered nodes to output
202
- /** @var Node $filteredNode */
203
- foreach ($ filteredOutput as $ filteredNode ) {
204
- /** @phpstan-ignore-next-line undefined behaviour https://github.com/neos/neos-development-collection/issues/4507#issuecomment-1784123143 */
205
- if (!isset ($ outputNodeAggregateIds [$ filteredNode ->nodeAggregateId ->value ])) {
206
- $ output [] = $ filteredNode ;
207
- }
208
- }
209
- }
210
- }
211
-
212
- if ($ optimized === true ) {
213
- $ flowQuery ->setContext ($ output );
214
- }
215
-
216
- return $ optimized ;
217
- }
218
117
}
0 commit comments