From 3ea7b6d2d7f05fa87c838282f7fa09a768e06d76 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Mon, 22 Apr 2024 15:24:37 +0200 Subject: [PATCH 1/2] [PHPStan] Aligned baseline after the merge up --- phpstan-baseline.neon | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3fd62bf6b4..33d587093f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4165,11 +4165,6 @@ parameters: count: 1 path: src/bundle/Debug/IbexaDebugBundle.php - - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:display\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/Debug/Twig/DebugTemplate.php - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:display\\(\\) has parameter \\$blocks with no value type specified in iterable type array\\.$#" count: 1 @@ -4180,11 +4175,6 @@ parameters: count: 1 path: src/bundle/Debug/Twig/DebugTemplate.php - - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:doDisplay\\(\\) has no return type specified\\.$#" - count: 1 - path: src/bundle/Debug/Twig/DebugTemplate.php - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:doDisplay\\(\\) has parameter \\$blocks with no value type specified in iterable type array\\.$#" count: 1 @@ -4195,16 +4185,6 @@ parameters: count: 1 path: src/bundle/Debug/Twig/DebugTemplate.php - - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:getDebugInfo\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/bundle/Debug/Twig/DebugTemplate.php - - - - message: "#^Method Ibexa\\\\Bundle\\\\Debug\\\\Twig\\\\DebugTemplate\\:\\:getSourceContext\\(\\) should return Twig\\\\Source but returns string\\.$#" - count: 1 - path: src/bundle/Debug/Twig/DebugTemplate.php - - message: "#^Parameter \\#1 \\$haystack of function stripos expects string, string\\|false\\|null given\\.$#" count: 1 From 4ccc0fad65cffef375ee9586b6f27d42dc8a1349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20D=C4=99bi=C5=84ski?= <58430570+mateuszdebinski@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:20:06 +0200 Subject: [PATCH 2/2] IBX-7833: [PAPI] Implemented loading paginated relation list (#343) For more details see https://issues.ibexa.co/browse/IBX-7833 and https://github.com/ibexa/core/pull/343 Key changes: * Added ContentService::loadRelationList public API which supports pagination * Added ContentService::countRelations public API * [Tests] Added integration coverage for the new APIs * Deprecated ContentService::loadRelations API * Implemented persistence cache for the new APIs' results --- phpstan-baseline.neon | 3 +- src/contracts/Persistence/Content/Handler.php | 22 ++++ src/contracts/Repository/ContentService.php | 28 ++++- .../Decorator/ContentServiceDecorator.php | 10 ++ src/lib/Persistence/Cache/ContentHandler.php | 95 +++++++++++++++ .../Persistence/Legacy/Content/Gateway.php | 24 +++- .../Content/Gateway/DoctrineDatabase.php | 65 ++++++++-- .../Content/Gateway/ExceptionConversion.php | 32 +++++ .../Persistence/Legacy/Content/Handler.php | 17 +++ src/lib/Repository/ContentService.php | 81 ++++++++++-- .../SiteAccessAware/ContentService.php | 13 ++ .../settings/storage_engines/cache.yml | 4 + .../Core/Repository/ContentServiceTest.php | 115 +++++++++++++++--- .../Persistence/Cache/ContentHandlerTest.php | 72 +++++++++++ .../SiteAccessAware/ContentServiceTest.php | 2 + 15 files changed, 543 insertions(+), 40 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 33d587093f..e40d562fdc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -16167,7 +16167,7 @@ parameters: - message: "#^Cannot call method fetchAll\\(\\) on Doctrine\\\\DBAL\\\\ForwardCompatibility\\\\Result\\|int\\|string\\.$#" - count: 17 + count: 16 path: src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php - @@ -63259,4 +63259,3 @@ parameters: message: "#^Method Ibexa\\\\Tests\\\\Core\\\\Specification\\\\Content\\\\ContentTypeSpecificationTest\\:\\:providerForIsSatisfiedBy\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: tests/lib/Specification/Content/ContentTypeSpecificationTest.php - diff --git a/src/contracts/Persistence/Content/Handler.php b/src/contracts/Persistence/Content/Handler.php index 5c62180a3d..5bfb72ed37 100644 --- a/src/contracts/Persistence/Content/Handler.php +++ b/src/contracts/Persistence/Content/Handler.php @@ -283,6 +283,8 @@ public function removeRelation($relationId, $type, ?int $destinationContentId = /** * Loads relations from $sourceContentId. Optionally, loads only those with $type and $sourceContentVersionNo. * + * @deprecated 4.5.7 The "ContentService::loadRelations()" method is deprecated, will be removed in 5.0. + * * @param mixed $sourceContentId Source Content ID * @param mixed|null $sourceContentVersionNo Source Content Version, null if not specified * @param int|null $type {@see \Ibexa\Contracts\Core\Repository\Values\Content\Relation::COMMON, @@ -294,6 +296,26 @@ public function removeRelation($relationId, $type, ?int $destinationContentId = */ public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null); + /** + * Counts all outgoing relations for the given version. + */ + public function countRelations( + int $sourceContentId, + ?int $sourceContentVersionNo = null, + ?int $type = null + ): int; + + /** + * @return \Ibexa\Contracts\Core\Persistence\Content\Relation[] + */ + public function loadRelationList( + int $sourceContentId, + int $limit, + int $offset = 0, + ?int $sourceContentVersionNo = null, + ?int $type = null + ): array; + /** * Counts relations from $destinationContentId only against published versions. Optionally, count only those with $type. * diff --git a/src/contracts/Repository/ContentService.php b/src/contracts/Repository/ContentService.php index 8418461cdb..4db6c57b7f 100644 --- a/src/contracts/Repository/ContentService.php +++ b/src/contracts/Repository/ContentService.php @@ -30,6 +30,8 @@ */ interface ContentService { + public const DEFAULT_PAGE_SIZE = 25; + /** * Loads a content info object. * @@ -398,12 +400,36 @@ public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $dest * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo + * @deprecated 4.5.7 The "ContentService::loadRelations()" method is deprecated, will be removed in 5.0. * * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation[] */ public function loadRelations(VersionInfo $versionInfo): iterable; + /** + * Loads all outgoing relations for the given version. + * + * If the user is not allowed to read specific version then a returned `RelationList` will contain `UnauthorizedRelationListItem` + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException + * + * @see \Ibexa\Contracts\Core\Repository\Values\Content\RelationList\Item\UnauthorizedRelationListItem + */ + public function loadRelationList( + VersionInfo $versionInfo, + int $offset = 0, + int $limit = self::DEFAULT_PAGE_SIZE + ): RelationList; + + /** + * Counts all outgoing relations for the given version. + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException + */ + public function countRelations(VersionInfo $versionInfo): int; + /** * Counts all incoming relations for the given content object. * diff --git a/src/contracts/Repository/Decorator/ContentServiceDecorator.php b/src/contracts/Repository/Decorator/ContentServiceDecorator.php index f8e4c7e73a..3575c0df42 100644 --- a/src/contracts/Repository/Decorator/ContentServiceDecorator.php +++ b/src/contracts/Repository/Decorator/ContentServiceDecorator.php @@ -193,6 +193,16 @@ public function loadRelations(VersionInfo $versionInfo): iterable return $this->innerService->loadRelations($versionInfo); } + public function countRelations(VersionInfo $versionInfo): int + { + return $this->innerService->countRelations($versionInfo); + } + + public function loadRelationList(VersionInfo $versionInfo, int $offset = 0, int $limit = self::DEFAULT_PAGE_SIZE): RelationList + { + return $this->innerService->loadRelationList($versionInfo, $offset, $limit); + } + public function countReverseRelations(ContentInfo $contentInfo): int { return $this->innerService->countReverseRelations($contentInfo); diff --git a/src/lib/Persistence/Cache/ContentHandler.php b/src/lib/Persistence/Cache/ContentHandler.php index 6dc10dbc17..9047a4c182 100644 --- a/src/lib/Persistence/Cache/ContentHandler.php +++ b/src/lib/Persistence/Cache/ContentHandler.php @@ -31,6 +31,10 @@ class ContentHandler extends AbstractInMemoryPersistenceHandler implements Conte private const CONTENT_VERSION_LIST_IDENTIFIER = 'content_version_list'; private const CONTENT_VERSION_INFO_IDENTIFIER = 'content_version_info'; private const CONTENT_VERSION_IDENTIFIER = 'content_version'; + private const CONTENT_RELATIONS_COUNT_WITH_VERSION_TYPE_IDENTIFIER = 'content_relations_count_with_by_version_type_suffix'; + private const CONTENT_RELATION_IDENTIFIER = 'content_relation'; + private const CONTENT_RELATIONS_LIST_IDENTIFIER = 'content_relations_list'; + private const CONTENT_RELATIONS_LIST_WITH_VERSION_TYPE_IDENTIFIER = 'content_relations_list_with_by_version_type_suffix'; private const CONTENT_REVERSE_RELATIONS_COUNT_IDENTIFIER = 'content_reverse_relations_count'; private const RELATION_IDENTIFIER = 'relation'; @@ -517,6 +521,97 @@ public function loadRelations($sourceContentId, $sourceContentVersionNo = null, return $this->persistenceHandler->contentHandler()->loadRelations($sourceContentId, $sourceContentVersionNo, $type); } + public function countRelations(int $sourceContentId, ?int $sourceContentVersionNo = null, ?int $type = null): int + { + $cacheItem = $this->cache->getItem( + $this->cacheIdentifierGenerator->generateKey( + self::CONTENT_RELATIONS_COUNT_WITH_VERSION_TYPE_IDENTIFIER, + [$sourceContentId, $sourceContentVersionNo, $type], + true + ) + ); + + if ($cacheItem->isHit()) { + $this->logger->logCacheHit(['content' => $sourceContentId, 'version' => $sourceContentVersionNo, 'type' => $type]); + + return $cacheItem->get(); + } + + $this->logger->logCacheMiss(['content' => $sourceContentId, 'version' => $sourceContentVersionNo, 'type' => $type]); + $relationsCount = $this->persistenceHandler->contentHandler()->countRelations( + $sourceContentId, + $sourceContentVersionNo, + $type + ); + $cacheItem->set($relationsCount); + $tags = [ + $this->cacheIdentifierGenerator->generateTag( + self::CONTENT_IDENTIFIER, + [$sourceContentId] + ), + ]; + + $cacheItem->tag($tags); + $this->cache->save($cacheItem); + + return $relationsCount; + } + + public function loadRelationList( + int $sourceContentId, + int $limit, + int $offset = 0, + ?int $sourceContentVersionNo = null, + ?int $type = null + ): array { + return $this->getListCacheValue( + $this->cacheIdentifierGenerator->generateKey( + self::CONTENT_RELATIONS_LIST_WITH_VERSION_TYPE_IDENTIFIER, + [$sourceContentId, $limit, $offset, $sourceContentVersionNo, $type], + true + ), + function () use ($sourceContentId, $limit, $offset, $sourceContentVersionNo, $type): array { + return $this->persistenceHandler->contentHandler()->loadRelationList( + $sourceContentId, + $limit, + $offset, + $sourceContentVersionNo, + $type + ); + }, + function (Relation $relation): array { + return [ + $this->cacheIdentifierGenerator->generateTag( + self::CONTENT_RELATION_IDENTIFIER, + [$relation->destinationContentId] + ), + $this->cacheIdentifierGenerator->generateTag( + self::CONTENT_IDENTIFIER, + [$relation->destinationContentId] + ), + ]; + }, + function (Relation $relation): array { + return [ + $this->cacheIdentifierGenerator->generateKey(self::CONTENT_IDENTIFIER, [$relation->destinationContentId], true), + ]; + }, + function () use ($sourceContentId): array { + return [ + $this->cacheIdentifierGenerator->generateTag( + self::CONTENT_RELATIONS_LIST_IDENTIFIER, + [$sourceContentId] + ), + $this->cacheIdentifierGenerator->generateTag( + self::CONTENT_IDENTIFIER, + [$sourceContentId] + ), + ]; + }, + [$sourceContentId, $limit, $offset, $sourceContentVersionNo, $type] + ); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Persistence/Legacy/Content/Gateway.php b/src/lib/Persistence/Legacy/Content/Gateway.php index 260e986c57..95f12bc0b4 100644 --- a/src/lib/Persistence/Legacy/Content/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Gateway.php @@ -361,7 +361,29 @@ abstract public function loadRelations( ): array; /** - * Count number of related to/from $contentId. + * Counts number of related to/from $contentId. + */ + abstract public function countRelations( + int $contentId, + ?int $contentVersionNo = null, + ?int $relationType = null + ): int; + + /** + * Loads paginated data of related to/from $contentId. + * + * @return array> + */ + abstract public function listRelations( + int $contentId, + int $limit, + int $offset = 0, + ?int $contentVersionNo = null, + ?int $relationType = null + ): array; + + /** + * Counts number of related to/from $contentId. */ abstract public function countReverseRelations(int $contentId, ?int $relationType = null): int; diff --git a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php index 8c0ecdc4d8..29d1569b52 100644 --- a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php @@ -1387,18 +1387,61 @@ public function loadRelations( ?int $relationType = null ): array { $query = $this->queryBuilder->createRelationFindQueryBuilder(); + $query = $this->prepareRelationQuery($query, $contentId, $contentVersionNo, $relationType); + + return $query->execute()->fetchAllAssociative(); + } + + public function countRelations( + int $contentId, + ?int $contentVersionNo = null, + ?int $relationType = null + ): int { + $query = $this->connection->createQueryBuilder(); + $query->select($this->databasePlatform->getCountExpression('l.id')) + ->from(self::CONTENT_RELATION_TABLE, 'l'); + + $query = $this->prepareRelationQuery($query, $contentId, $contentVersionNo, $relationType); + + return (int)$query->execute()->fetchOne(); + } + + public function listRelations( + int $contentId, + int $limit, + int $offset = 0, + ?int $contentVersionNo = null, + ?int $relationType = null + ): array { + $query = $this->queryBuilder->createRelationFindQueryBuilder(); + $query = $this->prepareRelationQuery($query, $contentId, $contentVersionNo, $relationType); + + $query->setFirstResult($offset) + ->setMaxResults($limit); + + $query->orderBy('l.id', 'DESC'); + + return $query->execute()->fetchAllAssociative(); + } + + private function prepareRelationQuery( + DoctrineQueryBuilder $query, + int $contentId, + ?int $contentVersionNo = null, + ?int $relationType = null + ): DoctrineQueryBuilder { $expr = $query->expr(); $query ->innerJoin( 'l', - 'ezcontentobject', - 'ezcontentobject_to', - $expr->andX( - 'l.to_contentobject_id = ezcontentobject_to.id', - 'ezcontentobject_to.status = :status' + self::CONTENT_ITEM_TABLE, + 'c_to', + $expr->and( + 'l.to_contentobject_id = c_to.id', + 'c_to.status = :status' ) ) - ->where( + ->andWhere( 'l.from_contentobject_id = :content_id' ) ->setParameter( @@ -1409,7 +1452,7 @@ public function loadRelations( ->setParameter('content_id', $contentId, ParameterType::INTEGER); // source version number - if (null !== $contentVersionNo) { + if ($contentVersionNo !== null) { $query ->andWhere('l.from_contentobject_version = :version_no') ->setParameter('version_no', $contentVersionNo, ParameterType::INTEGER); @@ -1417,10 +1460,10 @@ public function loadRelations( // from published version only $query ->innerJoin( - 'ezcontentobject_to', - 'ezcontentobject', + 'c_to', + self::CONTENT_ITEM_TABLE, 'c', - $expr->andX( + $expr->and( 'c.id = l.from_contentobject_id', 'c.current_version = l.from_contentobject_version' ) @@ -1442,7 +1485,7 @@ public function loadRelations( ->setParameter('relation_type', $relationType, ParameterType::INTEGER); } - return $query->execute()->fetchAll(FetchMode::ASSOCIATIVE); + return $query; } public function countReverseRelations(int $toContentId, ?int $relationType = null): int diff --git a/src/lib/Persistence/Legacy/Content/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Gateway/ExceptionConversion.php index 126b3c58f9..f4e8f9dc32 100644 --- a/src/lib/Persistence/Legacy/Content/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Gateway/ExceptionConversion.php @@ -392,6 +392,38 @@ public function loadRelations( } } + public function countRelations( + int $contentId, + ?int $contentVersionNo = null, + ?int $relationType = null + ): int { + try { + return $this->innerGateway->countRelations($contentId, $contentVersionNo, $relationType); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + + public function listRelations( + int $contentId, + int $limit, + int $offset = 0, + ?int $contentVersionNo = null, + ?int $relationType = null + ): array { + try { + return $this->innerGateway->listRelations( + $contentId, + $limit, + $offset, + $contentVersionNo, + $relationType + ); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + public function countReverseRelations(int $contentId, ?int $relationType = null): int { try { diff --git a/src/lib/Persistence/Legacy/Content/Handler.php b/src/lib/Persistence/Legacy/Content/Handler.php index 1165fcb9e8..88cef6dd00 100644 --- a/src/lib/Persistence/Legacy/Content/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Handler.php @@ -822,6 +822,23 @@ public function loadRelations($sourceContentId, $sourceContentVersionNo = null, ); } + public function countRelations(int $sourceContentId, ?int $sourceContentVersionNo = null, ?int $type = null): int + { + return $this->contentGateway->countRelations($sourceContentId, $sourceContentVersionNo, $type); + } + + public function loadRelationList( + int $sourceContentId, + int $limit, + int $offset = 0, + ?int $sourceContentVersionNo = null, + ?int $type = null + ): array { + return $this->mapper->extractRelationsFromRows( + $this->contentGateway->listRelations($sourceContentId, $limit, $offset, $sourceContentVersionNo, $type) + ); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/ContentService.php b/src/lib/Repository/ContentService.php index 5f426bab3f..5417a3d710 100644 --- a/src/lib/Repository/ContentService.php +++ b/src/lib/Repository/ContentService.php @@ -1991,15 +1991,6 @@ public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $dest return $this->internalLoadContentById($content->id); } - /** - * Loads all outgoing relations for the given version. - * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation[] - */ public function loadRelations(APIVersionInfo $versionInfo): iterable { if ($versionInfo->isPublished()) { @@ -2048,6 +2039,78 @@ protected function internalLoadRelations(APIVersionInfo $versionInfo): array return $relations; } + public function countRelations(APIVersionInfo $versionInfo): int + { + $function = $versionInfo->isPublished() ? 'read' : 'versionread'; + + if (!$this->permissionResolver->canUser('content', $function, $versionInfo)) { + return 0; + } + + $contentInfo = $versionInfo->getContentInfo(); + + return $this->persistenceHandler->contentHandler()->countRelations( + $contentInfo->id, + $versionInfo->versionNo + ); + } + + public function loadRelationList(APIVersionInfo $versionInfo, int $offset = 0, int $limit = self::DEFAULT_PAGE_SIZE): RelationList + { + $function = $versionInfo->isPublished() ? 'read' : 'versionread'; + + $list = new RelationList(); + + if (!$this->permissionResolver->canUser('content', $function, $versionInfo)) { + return $list; + } + + $contentInfo = $versionInfo->getContentInfo(); + $list->totalCount = $this->persistenceHandler->contentHandler()->countRelations( + $contentInfo->id, + $versionInfo->versionNo + ); + + if ($list->totalCount === 0) { + return $list; + } + + $persistenceRelationList = $this->persistenceHandler->contentHandler()->loadRelationList( + $contentInfo->id, + $limit, + $offset, + $versionInfo->versionNo, + ); + + $destinationContentIds = array_column($persistenceRelationList, 'destinationContentId'); + $destinationContentInfos = $this->persistenceHandler->contentHandler()->loadContentInfoList($destinationContentIds); + + foreach ($persistenceRelationList as $persistenceRelation) { + $contentId = $persistenceRelation->destinationContentId; + $destinationContentInfo = $this->contentDomainMapper->buildContentInfoDomainObject( + $destinationContentInfos[$contentId] + ); + + if (!$this->permissionResolver->canUser('content', 'read', $destinationContentInfo)) { + $list->items[] = new UnauthorizedRelationListItem( + 'content', + 'read', + ['contentId' => $destinationContentInfo->id] + ); + + continue; + } + $relation = $this->contentDomainMapper->buildRelationDomainObject( + $persistenceRelation, + $contentInfo, + $destinationContentInfo + ); + $list->items[] = new RelationListItem($relation); + } + + return $list; + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/ContentService.php b/src/lib/Repository/SiteAccessAware/ContentService.php index de504329c4..f931f59b1f 100644 --- a/src/lib/Repository/SiteAccessAware/ContentService.php +++ b/src/lib/Repository/SiteAccessAware/ContentService.php @@ -196,6 +196,19 @@ public function loadRelations(VersionInfo $versionInfo): iterable return $this->service->loadRelations($versionInfo); } + public function countRelations(VersionInfo $versionInfo): int + { + return $this->service->countRelations($versionInfo); + } + + public function loadRelationList( + VersionInfo $versionInfo, + int $offset = 0, + int $limit = self::DEFAULT_PAGE_SIZE + ): RelationList { + return $this->service->loadRelationList($versionInfo, $offset, $limit); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Resources/settings/storage_engines/cache.yml b/src/lib/Resources/settings/storage_engines/cache.yml index ec1941340f..48bb148387 100644 --- a/src/lib/Resources/settings/storage_engines/cache.yml +++ b/src/lib/Resources/settings/storage_engines/cache.yml @@ -31,6 +31,8 @@ parameters: content_type_group_with_id_suffix: 'ctg-%s-bi' content_type_group_list: 'ctgl-%s' content_type_list_by_group: 'ctlbg-%s' + content_relation: 'cr-%s' + content_relations_list: 'crl-%s' image_variation: 'ig' image_variation_name: 'ign-%s' image_variation_siteaccess: 'igs-%s' @@ -117,6 +119,8 @@ parameters: content_info: 'ci-%s' content_info_by_remote_id: 'cibri-%s' content_locations: 'cl-%s' + content_relations_count_with_by_version_type_suffix: 'crc-%%s-v-%%s-t-%%s' + content_relations_list_with_by_version_type_suffix: 'crl-%%s-l-%%s-o-%%s-v-%%s-t-%%s' content_reverse_relations_count: 'crrc-%s' content_version_info: 'cvi-%s' content_version_list: 'c-%s-vl' diff --git a/tests/integration/Core/Repository/ContentServiceTest.php b/tests/integration/Core/Repository/ContentServiceTest.php index 85bb0669e8..570f5a7609 100644 --- a/tests/integration/Core/Repository/ContentServiceTest.php +++ b/tests/integration/Core/Repository/ContentServiceTest.php @@ -3540,22 +3540,7 @@ public function testAddRelationThrowsBadStateException() */ public function testLoadRelations() { - $draft = $this->createContentDraftVersion1(); - - $media = $this->contentService->loadContentInfoByRemoteId(self::MEDIA_REMOTE_ID); - $demoDesign = $this->contentService->loadContentInfoByRemoteId(self::DEMO_DESIGN_REMOTE_ID); - - // Create relation between new content object and "Media" page - $this->contentService->addRelation( - $draft->getVersionInfo(), - $media - ); - - // Create another relation with the "Demo Design" page - $this->contentService->addRelation( - $draft->getVersionInfo(), - $demoDesign - ); + $draft = $this->createContentWithRelations(); $relations = $this->contentService->loadRelations($draft->getVersionInfo()); @@ -3698,6 +3683,84 @@ public function testLoadRelationsSkipsDraftContent() ); } + public function testCountRelations(): void + { + $draft = $this->createContentWithRelations(); + + self::assertEquals(2, $this->contentService->countRelations($draft->getVersionInfo())); + } + + public function testCountRelationsReturnsZeroByDefault(): void + { + $draft = $this->createContentDraftVersion1(); + + self::assertSame(0, $this->contentService->countRelations($draft->getVersionInfo())); + } + + public function testCountRelationsForUnauthorizedUser(): void + { + $draft = $this->createContentWithRelations(); + $mediaUser = $this->createMediaUserVersion1(); + $this->permissionResolver->setCurrentUserReference($mediaUser); + + self::assertSame(0, $this->contentService->countRelations($draft->getVersionInfo())); + } + + public function testLoadRelationList(): void + { + $draft = $this->createContentWithRelations(); + $relationList = $this->contentService->loadRelationList($draft->getVersionInfo()); + $media = $this->contentService->loadContentInfoByRemoteId(self::MEDIA_REMOTE_ID); + $demoDesign = $this->contentService->loadContentInfoByRemoteId(self::DEMO_DESIGN_REMOTE_ID); + + self::assertSame(2, $relationList->totalCount); + + $relation1 = $relationList->items[0]->getRelation(); + $relation2 = $relationList->items[1]->getRelation(); + + self::assertNotNull($relation1); + self::assertNotNull($relation2); + + self::assertEquals( + $demoDesign, + $relation1->getDestinationContentInfo() + ); + + self::assertEquals( + $media, + $relation2->getDestinationContentInfo() + ); + } + + public function testLoadRelationListWithPagination(): void + { + $draft = $this->createContentWithRelations(); + $versionInfo = $draft->getVersionInfo(); + $media = $this->contentService->loadContentInfoByRemoteId(self::MEDIA_REMOTE_ID); + $demoDesign = $this->contentService->loadContentInfoByRemoteId(self::DEMO_DESIGN_REMOTE_ID); + + $relationPage1 = $this->contentService->loadRelationList($versionInfo, 0, 1); + $relationPage2 = $this->contentService->loadRelationList($versionInfo, 1, 2); + + self::assertSame(2, $relationPage1->totalCount); + self::assertSame(2, $relationPage2->totalCount); + + $relation1 = $relationPage1->items[0]->getRelation(); + $relation2 = $relationPage2->items[0]->getRelation(); + + self::assertNotNull($relation1); + self::assertNotNull($relation2); + + self::assertEquals( + $demoDesign, + $relation1->getDestinationContentInfo() + ); + self::assertEquals( + $media, + $relation2->getDestinationContentInfo() + ); + } + /** * Test for the countReverseRelations() method. * @@ -6668,6 +6731,26 @@ private function createContentWithReverseRelations(array $drafts) return $contentWithReverseRelations; } + + private function createContentWithRelations(): Content + { + $draft = $this->createContentDraftVersion1(); + $versionInfo = $draft->getVersionInfo(); + + $media = $this->contentService->loadContentInfoByRemoteId(self::MEDIA_REMOTE_ID); + $demoDesign = $this->contentService->loadContentInfoByRemoteId(self::DEMO_DESIGN_REMOTE_ID); + $this->contentService->addRelation( + $versionInfo, + $media + ); + + $this->contentService->addRelation( + $versionInfo, + $demoDesign + ); + + return $draft; + } } class_alias(ContentServiceTest::class, 'eZ\Publish\API\Repository\Tests\ContentServiceTest'); diff --git a/tests/lib/Persistence/Cache/ContentHandlerTest.php b/tests/lib/Persistence/Cache/ContentHandlerTest.php index 40f3e6822e..c7c43308fa 100644 --- a/tests/lib/Persistence/Cache/ContentHandlerTest.php +++ b/tests/lib/Persistence/Cache/ContentHandlerTest.php @@ -11,6 +11,7 @@ use Ibexa\Contracts\Core\Persistence\Content\CreateStruct; use Ibexa\Contracts\Core\Persistence\Content\Handler as SPIContentHandler; use Ibexa\Contracts\Core\Persistence\Content\MetadataUpdateStruct; +use Ibexa\Contracts\Core\Persistence\Content\Relation; use Ibexa\Contracts\Core\Persistence\Content\Relation as SPIRelation; use Ibexa\Contracts\Core\Persistence\Content\Relation\CreateStruct as RelationCreateStruct; use Ibexa\Contracts\Core\Persistence\Content\UpdateStruct; @@ -82,10 +83,25 @@ public function providerForCachedLoadMethodsHit(): array $info = new ContentInfo(['id' => 2]); $version = new VersionInfo(['versionNo' => 1, 'contentInfo' => $info]); $content = new Content(['fields' => [], 'versionInfo' => $version]); + $relation = new Relation(); + $relation->id = 1; + $relation->sourceContentId = 2; + $relation->sourceContentVersionNo = 2; + $relation->destinationContentId = 1; + $relation->type = 1; + $relationList[1] = $relation; // string $method, array $arguments, string $key, array? $tagGeneratingArguments, array? $tagGeneratingResults, array? $keyGeneratingArguments, array? $keyGeneratingResults, mixed? $data, bool $multi = false, array $additionalCalls return [ ['countReverseRelations', [2], 'ibx-crrc-2', null, null, [['content_reverse_relations_count', [2], true]], ['ibx-crrc-2'], 10], + ['countRelations', [2], 'ibx-crc-2-v--t-', null, null, [['content_relations_count_with_by_version_type_suffix', [2, null, null], true]], ['ibx-crc-2-v--t-'], 10], + ['countRelations', [2, 2], 'ibx-crc-2-v-2-t-', null, null, [['content_relations_count_with_by_version_type_suffix', [2, 2, null], true]], ['ibx-crc-2-v-2-t-'], 10], + ['countRelations', [2, null, 1], 'ibx-crc-2-v--t-1', null, null, [['content_relations_count_with_by_version_type_suffix', [2, null, 1], true]], ['ibx-crc-2-v--t-1'], 10], + ['countRelations', [2, 2, 1], 'ibx-crc-2-v-2-t-1', null, null, [['content_relations_count_with_by_version_type_suffix', [2, 2, 1], true]], ['ibx-crc-2-v-2-t-1'], 10], + ['loadRelationList', [2, 1, 0], 'ibx-crl-2-l-1-o-0-v--t-', null, null, [['content_relations_list_with_by_version_type_suffix', [2, 1, 0, null, null], true]], ['ibx-crl-2-l-1-o-0-v--t-'], $relationList], + ['loadRelationList', [2, 1, 0, 2], 'ibx-crl-2-l-1-o-0-v-2-t-', null, null, [['content_relations_list_with_by_version_type_suffix', [2, 1, 0, 2, null], true]], ['ibx-crl-2-l-1-o-0-v-2-t-'], $relationList], + ['loadRelationList', [2, 1, 0, null, 1], 'ibx-crl-2-l-1-o-0-v--t-1', null, null, [['content_relations_list_with_by_version_type_suffix', [2, 1, 0, null, 1], true]], ['ibx-crl-2-l-1-o-0-v--t-1'], $relationList], + ['loadRelationList', [2, 1, 0, 2, 1], 'ibx-crl-2-l-1-o-0-v-2-t-1', null, null, [['content_relations_list_with_by_version_type_suffix', [2, 1, 0, 2, 1], true]], ['ibx-crl-2-l-1-o-0-v-2-t-1'], $relationList], ['load', [2, 1], 'ibx-c-2-1-' . ContentHandler::ALL_TRANSLATIONS_KEY, null, null, [['content', [], true]], ['ibx-c'], $content], ['load', [2, 1, ['eng-GB', 'eng-US']], 'ibx-c-2-1-eng-GB|eng-US', null, null, [['content', [], true]], ['ibx-c'], $content], ['load', [2], 'ibx-c-2-' . ContentHandler::ALL_TRANSLATIONS_KEY, null, null, [['content', [], true]], ['ibx-c'], $content], @@ -131,6 +147,62 @@ public function providerForCachedLoadMethodsMiss(): array ['ibx-crrc-2'], 10, ], + [ + 'countRelations', + [2], + 'ibx-crc-2-v--t-', + [ + ['content', [2], false], + ], + ['c-2'], + [ + ['content_relations_count_with_by_version_type_suffix', [2, null, null], true], + ], + ['ibx-crc-2-v--t-'], + 10, + ], + [ + 'countRelations', + [2, 3], + 'ibx-crc-2-v-3-t-', + [ + ['content', [2], false], + ], + ['c-2'], + [ + ['content_relations_count_with_by_version_type_suffix', [2, 3, null], true], + ], + ['ibx-crc-2-v-3-t-'], + 10, + ], + [ + 'countRelations', + [2, null, 1], + 'ibx-crc-2-v--t-1', + [ + ['content', [2], false], + ], + ['c-2'], + [ + ['content_relations_count_with_by_version_type_suffix', [2, null, 1], true], + ], + ['ibx-crc-2-v--t-1'], + 10, + ], + [ + 'countRelations', + [2, 3, 1], + 'ibx-crc-2-v-3-t-', + [ + ['content', [2], false], + ], + ['c-2'], + [ + ['content_relations_count_with_by_version_type_suffix', [2, 3, 1], true], + ], + ['ibx-crc-2-v-3-t-'], + 10, + ], [ 'load', [2, 1], diff --git a/tests/lib/Repository/SiteAccessAware/ContentServiceTest.php b/tests/lib/Repository/SiteAccessAware/ContentServiceTest.php index 185bfde543..ee0f691163 100644 --- a/tests/lib/Repository/SiteAccessAware/ContentServiceTest.php +++ b/tests/lib/Repository/SiteAccessAware/ContentServiceTest.php @@ -103,6 +103,8 @@ public function providerForPassTroughMethods(): array ['copyContent', [$contentInfo, $locationCreateStruct, $versionInfo], $content], ['loadRelations', [$versionInfo], [$relation]], + ['loadRelationList', [$versionInfo], $relationList], + ['countRelations', [$versionInfo], 0], ['countReverseRelations', [$contentInfo], 0],