Skip to content

Commit d511a60

Browse files
refactor: enhance validation for object attribute query values to disallow mixed lists and improve depth handling
1 parent 9534938 commit d511a60

2 files changed

Lines changed: 36 additions & 52 deletions

File tree

src/Database/Validator/Query/Filter.php

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -301,63 +301,70 @@ protected function isEmpty(array $values): bool
301301
* Validate object attribute query values.
302302
*
303303
* Disallows ambiguous nested structures like:
304-
* ['a' => [1, 'b' => [212]]]
305-
* ['role' => ['name' => [...], 'ex' => [...]]]
304+
* ['a' => [1, 'b' => [212]]] // mixed list
305+
* ['role' => ['name' => [...], 'ex' => [...]]] // multiple nested paths
306306
*
307307
* but allows:
308-
* ['a' => [1, 2], 'b' => [212]]
308+
* ['a' => [1, 2], 'b' => [212]] // multiple top-level paths
309+
* ['projects' => [[...]]] // list of objects
309310
*
310311
* @param array<mixed> $values
311312
* @return bool
312313
*/
313314
private function isValidObjectQueryValues(array $values): bool
314315
{
315-
$validateNode = function (mixed $node, bool $isInList = false) use (&$validateNode): bool {
316+
$validate = function (mixed $node, int $depth = 0, bool $inDataContext = false) use (&$validate): bool {
316317
if (!\is_array($node)) {
317318
return true;
318319
}
319320

320321
if (\array_is_list($node)) {
322+
// Check if list is mixed (has both assoc arrays and non-assoc items)
323+
$hasAssoc = false;
324+
$hasNonAssoc = false;
325+
321326
foreach ($node as $item) {
322-
if (!$validateNode($item, true)) {
323-
return false;
327+
if (\is_array($item) && !\array_is_list($item)) {
328+
$hasAssoc = true;
329+
} else {
330+
$hasNonAssoc = true;
324331
}
325332
}
326333

327-
return true;
328-
}
334+
// Mixed lists are invalid
335+
if ($hasAssoc && $hasNonAssoc) {
336+
return false;
337+
}
329338

330-
if (!$isInList && \count($node) !== 1) {
331-
return false;
332-
}
339+
// If list contains associative arrays, they're data objects
340+
$enterDataContext = $hasAssoc;
333341

334-
if ($isInList) {
335-
foreach ($node as $value) {
336-
// When in a list context, values of associative arrays are also object structures,
337-
// not navigation paths, so pass isInList=true for nested associative arrays
338-
$valueIsInList = \is_array($value) && !\array_is_list($value);
339-
if (!$validateNode($value, $valueIsInList)) {
342+
foreach ($node as $item) {
343+
if (!$validate($item, $depth + 1, $enterDataContext || $inDataContext)) {
340344
return false;
341345
}
342346
}
343347
return true;
344348
}
345349

346-
$firstKey = \array_key_first($node);
347-
return $validateNode($node[$firstKey], false);
348-
};
349-
350-
// Check if values is an indexed array (list)
351-
// If so, its elements should be validated with isInList=true
352-
$valuesIsIndexed = \array_is_list($values);
353-
354-
foreach ($values as $value) {
355-
if (!$validateNode($value, $valuesIsIndexed)) {
350+
// Associative array
351+
// If in data context, multiple keys are OK (it's an object)
352+
// If depth > 0 and NOT in data context, only 1 key allowed (navigation)
353+
if (!$inDataContext && $depth > 0 && \count($node) !== 1) {
356354
return false;
357355
}
358-
}
359356

360-
return true;
357+
// Validate all values
358+
foreach ($node as $value) {
359+
if (!$validate($value, $depth + 1, $inDataContext)) {
360+
return false;
361+
}
362+
}
363+
364+
return true;
365+
};
366+
367+
return $validate($values, 0, false);
361368
}
362369

363370
/**

tests/unit/Validator/QueriesTest.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,5 @@ public function testValid(): void
9292
]),
9393
$validator->getDescription()
9494
);
95-
96-
// Object attribute query: disallowed nested multiple keys in same level
97-
$this->assertFalse(
98-
$validator->isValid([
99-
Query::equal('meta', [
100-
['a' => [1, 'b' => [212]]],
101-
]),
102-
])
103-
);
104-
105-
// Object attribute query: disallowed complex multi-key nested structure
106-
$this->assertFalse(
107-
$validator->isValid([
108-
Query::contains('meta', [
109-
[
110-
'role' => [
111-
'name' => ['test1', 'test2'],
112-
'ex' => ['new' => 'test1'],
113-
],
114-
],
115-
]),
116-
])
117-
);
11895
}
11996
}

0 commit comments

Comments
 (0)