From 4398f543dcd19b34c4dddcc4a5006022b2d79e15 Mon Sep 17 00:00:00 2001 From: Jeremy Dormitzer Date: Sun, 25 Nov 2018 11:01:35 -0500 Subject: [PATCH] [WIP] Begin implementing support for nested/sequential queries --- src/Objects/ObjectsService.php | 77 +++++++++++++++++++++++++--------- test/ObjectsServiceTest.php | 35 ++++++++++++++++ 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/Objects/ObjectsService.php b/src/Objects/ObjectsService.php index a2502a0..a336f42 100644 --- a/src/Objects/ObjectsService.php +++ b/src/Objects/ObjectsService.php @@ -31,6 +31,7 @@ class ObjectsService // TODO validate object fields? // TODO don't create the object if it already exists // i.e. (an 'id' field exists with the same value as this one) + // TODO attempt to fetch and create any values that are URLs $object = new ActivityPubObject(); $this->entityManager->persist( $object ); foreach ( $fields as $name => $value ) { @@ -86,28 +87,66 @@ class ObjectsService */ public function query( $queryTerms ) { - // TODO make it search for nested objects like the comment says $qb = $this->entityManager->createQueryBuilder(); - $qb->select( 'object' ) - ->from( '\ActivityPub\Entities\ActivityPubObject', 'object' ) - ->join( 'object.fields', 'field' ); - foreach ( $queryTerms as $fieldName => $fieldValue ) { - if ( is_array( $fieldValue ) ) { - // The following two branches will need to be recursive - if ( Util::isAssoc( $fieldValue ) ) { - // TODO support querying for associative arrays (nested objects) - } else { - // TODO support querying for sequential arrays - } - } else { - $qb->where( $qb->expr()->andX( - $qb->expr()->like( 'field.name', $qb->expr()->literal( $fieldName ) ), - $qb->expr()->like( 'field.value', $qb->expr()->literal( $fieldValue ) ) - ) ); - } - } + $depth = 0; + $qb->select( "object$depth" ) + ->from( '\ActivityPub\Entities\ActivityPubObject', "object$depth" ) + ->join( "object$depth.fields", "field$depth" ) + ->where( $this->getWhereExpr( $qb, $queryTerms, $depth ) ); $query = $qb->getQuery(); return $query->getResult(); } + + /** + * Generates the expression that gets passed into the query WHERE clause + * + * This function is recursive; it traverses the query tree to build up the + * final expression + * + * @param QueryBuilder $qb The query builder that the WHERE clause will be attached to + * @param array $queryTerms The query terms from which to generate the expressions + * @param int $depth The recursion depth + * @return Expr The expression + */ + protected function getWhereExpr( &$qb, $queryTerms, $depth = 0 ) + { + $nextDepth = $depth + 1; + $exprs = array(); + foreach( $queryTerms as $fieldName => $fieldValue ) { + if ( is_array( $fieldValue ) ) { + if ( Util::isAssoc( $fieldValue ) ) { + $subQuery = $this->entityManager->createQueryBuilder(); + $subQuery->select( "object$nextDepth" ) + ->from( '\ActivityPub\Entities\ActivityPubObject', "object$nextDepth" ) + ->join( "object$nextDepth.fields", "field$nextDepth" ) + ->where( $this->getWhereExpr( $subQuery, $fieldValue, $nextDepth ) ); + $exprs[] = $qb->expr()->in( + "field$depth.targetObject", + $subQuery->getDql() + ); + } else { + $subExprs = array(); + foreach ( $fieldValue as $subFieldName => $subFieldValue ) { + $subExprs[] = $this->getWhereExpr( + $qb, array( $subFieldName => $subFieldValue ), $nextDepth + ); + } + $exprs[] = call_user_func_array( + array( $qb->expr(), 'orX' ), + $subExprs + ); + } + } else { + $exprs[] = $qb->expr()->andX( + $qb->expr()->like( "field$depth.name", $qb->expr()->literal( $fieldName ) ), + $qb->expr()->like( "field$depth.value", $qb->expr()->literal( $fieldValue ) ) + ); + } + } + return call_user_func_array( + array( $qb->expr(), 'andX' ), + $exprs + ); + } } ?> diff --git a/test/ObjectsServiceTest.php b/test/ObjectsServiceTest.php index 0a45d72..bb420e6 100644 --- a/test/ObjectsServiceTest.php +++ b/test/ObjectsServiceTest.php @@ -527,5 +527,40 @@ class ObjectsServiceTest extends SQLiteTestCase $this->assertContainsOnlyInstancesOf( ActivityPubObject::class, $results ); $this->assertEquals( $object, $results[0] ); } + + public function testMultiNestedSequentialObjectQueryResults() + { + $fields = array( + 'id' => 'https://example.com/notes/1', + 'type' => 'Note', + 'content' => 'This is a note', + 'attributedTo' => array( + 'id' => 'https://example.com/actors/2', + 'type' => 'Person', + 'following' => array( + 'id' => 'https://example.com/collections/1', + 'type' => 'Collection', + 'items' => array( + array( 'id' => 'https://example.com/actors/1' ), + ), + ), + ), + ); + $object = $this->objectsService->createObject( $fields ); + $query = array( + 'attributedTo' => array( + 'following' => array( + 'items' => array( + array( 'id' => 'https://example.com/actors/1' ) + ), + ), + ), + ); + $results = $this->objectsService->query( $query ); + $this->assertCount( 1, $results ); + $this->assertContainsOnlyInstancesOf( ActivityPubObject::class, $results ); + $this->assertEquals( $object, $results[0] ); + $this->assertEquals( $fields, $results[0]->asArray() ); + } } ?>