1313use Flow \JSONPath \AccessHelper ;
1414use Flow \JSONPath \JSONPath ;
1515use Flow \JSONPath \JSONPathException ;
16+ use JsonException ;
1617use RuntimeException ;
1718
19+ use const JSON_THROW_ON_ERROR ;
20+ use const PREG_OFFSET_CAPTURE ;
21+ use const PREG_UNMATCHED_AS_NULL ;
22+
1823class QueryMatchFilter extends AbstractFilter
1924{
2025 protected const MATCH_QUERY_NEGATION_WRAPPED = '^(?<negate>!)\((?<logicalexpr>.+)\)$ ' ;
26+
2127 protected const MATCH_QUERY_NEGATION_UNWRAPPED = '^(?<negate>!)(?<logicalexpr>.+)$ ' ;
28+
2229 protected const MATCH_QUERY_OPERATORS = '
2330 (@\.(?<key>[^\s<>!=]+)|@\[[" \']?(?<keySquare>.*?)[" \']?\]|(?<node>@)|(%group(?<group>\d+)%))
2431 (\s*(?<operator>==|=~|=|<>|!==|!=|>=|<=|>|<|in|!in|nin)\s*(?<comparisonValue>.+?(?=(&&|$|\|\||%))))?
@@ -56,7 +63,7 @@ public function filter($collection): array
5663 //sanity check that our group is a group and not something within a string or regular expression
5764 if (\preg_match ('/ ' . static ::MATCH_QUERY_OPERATORS . '/x ' , $ test )) {
5865 $ filterGroups [$ i ] = $ test ;
59- $ filterExpression = \str_replace ($ matchesGroup [0 ], "%group $ i % " , $ filterExpression );
66+ $ filterExpression = \str_replace ($ matchesGroup [0 ], "%group { $ i } % " , $ filterExpression );
6067 }
6168 }
6269 }
@@ -71,16 +78,18 @@ public function filter($collection): array
7178 if (
7279 $ match === false
7380 || !isset ($ matches [1 ][0 ])
74- || isset ($ matches ['logicalandor ' ][array_key_last ($ matches ['logicalandor ' ])])
81+ || isset ($ matches ['logicalandor ' ][\ array_key_last ($ matches ['logicalandor ' ])])
7582 ) {
7683 throw new RuntimeException ('Malformed filter query ' );
7784 }
7885
7986 $ return = [];
87+ $ matchCount = \count ($ matches [0 ]);
8088
81- for ($ expressionPart = 0 ; $ expressionPart < \count ( $ matches [ 0 ]) ; $ expressionPart ++) {
89+ for ($ expressionPart = 0 ; $ expressionPart < $ matchCount ; $ expressionPart ++) {
8290 $ filteredCollection = $ collection ;
8391 $ logicalJoin = $ expressionPart > 0 ? $ matches ['logicalandor ' ][$ expressionPart - 1 ] : null ;
92+
8493 if ($ logicalJoin === '&& ' ) {
8594 //Restrict the nodes we need to look at to those already meeting criteria
8695 $ filteredCollection = $ return ;
@@ -92,6 +101,7 @@ public function filter($collection): array
92101 $ filter = '$[?( ' . $ filterGroups [$ matches ['group ' ][$ expressionPart ]] . ')] ' ;
93102 $ resolve = (new JSONPath ($ filteredCollection ))->find ($ filter )->getData ();
94103 $ return = $ resolve ;
104+
95105 continue ;
96106 }
97107
@@ -102,12 +112,13 @@ public function filter($collection): array
102112 $ comparisonValue = $ matches ['comparisonValue ' ][$ expressionPart ] ?? null ;
103113
104114 if (\is_string ($ comparisonValue )) {
105- $ comparisonValue = \preg_replace ('/^[ \']/ ' , '" ' , $ comparisonValue );
106- $ comparisonValue = \preg_replace ('/[ \']$/ ' , '" ' , $ comparisonValue );
115+ $ comparisonValue = \preg_replace ('/^ \'/ ' , '" ' , $ comparisonValue );
116+ $ comparisonValue = \preg_replace ('/ \'$/ ' , '" ' , $ comparisonValue );
117+
107118 try {
108- $ comparisonValue = \json_decode ($ comparisonValue , true , flags: JSON_THROW_ON_ERROR );
109- } catch (\ JsonException $ e ) {
110- //Leave $comparisonValue as raw (eg . regular express or non quote wrapped string)
119+ $ comparisonValue = \json_decode ($ comparisonValue , true , 512 , JSON_THROW_ON_ERROR );
120+ } catch (JsonException ) {
121+ //Leave $comparisonValue as raw (e.g . regular express or non quote wrapped string)
111122 }
112123 }
113124
@@ -116,14 +127,16 @@ public function filter($collection): array
116127 //Short-circuit, node already exists in output due to previous test
117128 continue ;
118129 }
119- $ selectedNode = null ;
120130
131+ $ selectedNode = null ;
121132 $ notNothing = AccessHelper::keyExists ($ node , $ key , $ this ->magicIsAllowed );
133+
122134 if ($ key ) {
123135 if ($ notNothing ) {
124136 $ selectedNode = AccessHelper::getValue ($ node , $ key , $ this ->magicIsAllowed );
125137 } elseif (\str_contains ($ key , '. ' )) {
126138 $ foundValue = (new JSONPath ($ node ))->find ($ key )->getData ();
139+
127140 if ($ foundValue ) {
128141 $ selectedNode = $ foundValue [0 ];
129142 $ notNothing = true ;
@@ -136,6 +149,7 @@ public function filter($collection): array
136149 }
137150
138151 $ comparisonResult = null ;
152+
139153 if ($ notNothing ) {
140154 $ comparisonResult = match ($ operator ) {
141155 null => AccessHelper::keyExists ($ node , $ key , $ this ->magicIsAllowed ) || (!$ key ),
@@ -165,6 +179,7 @@ public function filter($collection): array
165179
166180 //Keep out returned nodes in the same order they were defined in the original collection
167181 \ksort ($ return );
182+
168183 return $ return ;
169184 }
170185
@@ -177,14 +192,17 @@ protected function compareEquals($a, $b): bool
177192 {
178193 $ type_a = \gettype ($ a );
179194 $ type_b = \gettype ($ b );
195+
180196 if ($ type_a === $ type_b || ($ this ->isNumber ($ a ) && $ this ->isNumber ($ b ))) {
181197 //Primitives or Numbers
182198 if ($ a === null || \is_scalar ($ a )) {
199+ /** @noinspection TypeUnsafeComparisonInspection */
183200 return $ a == $ b ;
184201 }
185202 //Object/Array
186203 //@TODO array and object comparison
187204 }
205+
188206 return false ;
189207 }
190208
@@ -194,6 +212,7 @@ protected function compareLessThan($a, $b): bool
194212 //numerical and string comparison supported only
195213 return $ a < $ b ;
196214 }
215+
197216 return false ;
198217 }
199218}
0 commit comments