diff --git a/src/Database/Query.php b/src/Database/Query.php index d19b84259..147c463ad 100644 --- a/src/Database/Query.php +++ b/src/Database/Query.php @@ -393,7 +393,13 @@ public static function parseQuery(array $query): self if (\in_array($method, self::LOGICAL_TYPES)) { foreach ($values as $index => $value) { - $values[$index] = self::parseQuery($value); + if (\is_string($value)) { + $values[$index] = self::parse($value); + } elseif (\is_array($value)) { + $values[$index] = self::parseQuery($value); + } else { + throw new QueryException('Invalid nested query. Must be an array or string, got ' . \gettype($value)); + } } } diff --git a/tests/unit/QueryTest.php b/tests/unit/QueryTest.php index 0f1f69726..7d1414c0f 100644 --- a/tests/unit/QueryTest.php +++ b/tests/unit/QueryTest.php @@ -395,6 +395,40 @@ public function testParse(): void $this->assertEquals([], $query->getValues()); } + public function testParseNestedStringValues(): void + { + // Some clients serialize the children of a logical query as JSON strings + // rather than nested objects. Parsing must handle that without a TypeError. + $json = (string) \json_encode([ + 'method' => Query::TYPE_OR, + 'values' => [ + Query::equal('actors', ['Brad Pitt'])->toString(), + Query::equal('actors', ['Johnny Depp'])->toString(), + ], + ]); + + $query = Query::parse($json); + + /** @var array $queries */ + $queries = $query->getValues(); + $this->assertEquals(Query::TYPE_OR, $query->getMethod()); + $this->assertCount(2, $queries); + $this->assertEquals(Query::TYPE_EQUAL, $queries[0]->getMethod()); + $this->assertEquals('actors', $queries[0]->getAttribute()); + $this->assertEquals(['Brad Pitt'], $queries[0]->getValues()); + $this->assertEquals(Query::TYPE_EQUAL, $queries[1]->getMethod()); + $this->assertEquals(['Johnny Depp'], $queries[1]->getValues()); + + // A nested value that is neither an array nor a string is a clean + // QueryException, never an uncaught TypeError. + try { + Query::parse((string) \json_encode(['method' => Query::TYPE_OR, 'values' => [123]])); + $this->fail('Failed to throw exception'); + } catch (QueryException $e) { + $this->assertEquals('Invalid nested query. Must be an array or string, got integer', $e->getMessage()); + } + } + public function testIsMethod(): void { $this->assertTrue(Query::isMethod('equal'));