From 2b52ed368404c45695a54310d76af3b2a30aae29 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Aug 2025 10:05:50 +0200 Subject: [PATCH 01/14] IBX-10458: Implemented new Content Type search PHP API --- .../Persistence/Content/Type/Handler.php | 8 + .../Repository/ContentTypeService.php | 8 + .../Decorator/ContentTypeServiceDecorator.php | 6 + .../Values/ContentType/Query/Base.php | 45 +++++ .../ContentType/Query/ContentTypeQuery.php | 84 ++++++++ .../Criterion/ContainsFieldDefinitionIds.php | 41 ++++ .../Query/Criterion/ContentTypeGroupIds.php | 41 ++++ .../Query/Criterion/Identifiers.php | 41 ++++ .../ContentType/Query/Criterion/Ids.php | 41 ++++ .../ContentType/Query/Criterion/IsSystem.php | 31 +++ .../Query/Criterion/LogicalAnd.php | 13 ++ .../Query/Criterion/LogicalNot.php | 13 ++ .../Query/Criterion/LogicalOperator.php | 44 +++++ .../ContentType/Query/Criterion/LogicalOr.php | 13 ++ .../Query/CriterionHandlerInterface.php | 27 +++ .../ContentType/Query/CriterionInterface.php | 13 ++ .../Values/ContentType/Query/SortClause.php | 36 ++++ .../ContentType/Query/SortClause/Id.php | 19 ++ .../Query/SortClause/Identifier.php | 19 ++ .../Persistence/Cache/ContentTypeHandler.php | 11 ++ .../Legacy/Content/Type/Gateway.php | 8 + .../ContainsFieldDefinitionIds.php | 36 ++++ .../CriterionHandler/ContentTypeGroupIds.php | 36 ++++ .../Gateway/CriterionHandler/Identifiers.php | 36 ++++ .../Type/Gateway/CriterionHandler/Ids.php | 36 ++++ .../Gateway/CriterionHandler/IsSystem.php | 38 ++++ .../Gateway/CriterionHandler/LogicalAnd.php | 55 ++++++ .../Gateway/CriterionHandler/LogicalNot.php | 55 ++++++ .../Gateway/CriterionHandler/LogicalOr.php | 55 ++++++ .../ContainsFieldDefinitionIds.php | 37 ++++ .../ContentTypeGroupIds.php | 37 ++++ .../CriterionQueryBuilderInterface.php | 35 ++++ .../CriterionQueryBuilder/Identifiers.php | 37 ++++ .../Gateway/CriterionQueryBuilder/Ids.php | 37 ++++ .../CriterionQueryBuilder/IsSystem.php | 37 ++++ .../CriterionQueryBuilder/LogicalAnd.php | 42 ++++ .../CriterionVisitor/CriterionVisitor.php | 54 ++++++ .../Content/Type/Gateway/DoctrineDatabase.php | 108 ++++++++++- .../Type/Gateway/ExceptionConversion.php | 19 ++ .../Legacy/Content/Type/Handler.php | 17 ++ .../Content/Type/MemoryCachingHandler.php | 11 ++ src/lib/Repository/ContentTypeService.php | 20 +- .../SiteAccessAware/ContentTypeService.php | 6 + .../storage_engines/legacy/content_type.yml | 48 ++++- .../FindContentTypesTest.php | 183 ++++++++++++++++++ .../Type/Gateway/DoctrineDatabaseTest.php | 3 +- .../Legacy/Content/AbstractTestCase.php | 1 + 47 files changed, 1632 insertions(+), 9 deletions(-) create mode 100644 src/contracts/Repository/Values/ContentType/Query/Base.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalAnd.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalNot.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalOperator.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalOr.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/CriterionInterface.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/SortClause.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/SortClause/Id.php create mode 100644 src/contracts/Repository/Values/ContentType/Query/SortClause/Identifier.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php create mode 100644 tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php diff --git a/src/contracts/Persistence/Content/Type/Handler.php b/src/contracts/Persistence/Content/Type/Handler.php index a2a72698af..badabfb915 100644 --- a/src/contracts/Persistence/Content/Type/Handler.php +++ b/src/contracts/Persistence/Content/Type/Handler.php @@ -9,6 +9,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Type; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\CreateStruct as GroupCreateStruct; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; interface Handler { @@ -91,6 +92,13 @@ public function loadContentTypes($groupId, $status = Type::STATUS_DEFINED); */ public function loadContentTypeList(array $contentTypeIds): array; + public function countContentTypes(?ContentTypeQuery $query = null): int; + + /** + * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] + */ + public function findContentTypes(?ContentTypeQuery $query = null): array; + /** * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] */ diff --git a/src/contracts/Repository/ContentTypeService.php b/src/contracts/Repository/ContentTypeService.php index d17aafe53c..0bf80b9434 100644 --- a/src/contracts/Repository/ContentTypeService.php +++ b/src/contracts/Repository/ContentTypeService.php @@ -18,6 +18,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Contracts\Core\Repository\Values\User\User; interface ContentTypeService @@ -173,6 +174,13 @@ public function loadContentTypeDraft(int $contentTypeId, bool $ignoreOwnership = */ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLanguages = []): iterable; + /** + * @param list $prioritizedLanguages Used as prioritized language code on translated properties of returned object. + * + * @return array<\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType> + */ + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array; + /** * Get content type objects which belong to the given content type group. * diff --git a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php index 863e725d19..a1f59e05ee 100644 --- a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php +++ b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Contracts\Core\Repository\Values\User\User; abstract class ContentTypeServiceDecorator implements ContentTypeService @@ -107,6 +108,11 @@ public function loadContentTypeList( return $this->innerService->loadContentTypeList($contentTypeIds, $prioritizedLanguages); } + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + { + return $this->innerService->findContentTypes($query, $prioritizedLanguages); + } + public function loadContentTypes( ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = [] diff --git a/src/contracts/Repository/Values/ContentType/Query/Base.php b/src/contracts/Repository/Values/ContentType/Query/Base.php new file mode 100644 index 0000000000..763eb2476f --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Base.php @@ -0,0 +1,45 @@ +hasJoinedTable($query, Gateway::CONTENT_TYPE_GROUP_TABLE)) { + $query->innerJoin( + 'g', + Gateway::CONTENT_TYPE_GROUP_TABLE, + 'ctg', + 'g.contentclass_id = ctg.id' + ); + } + } + + protected function hasJoinedTable(QueryBuilder $queryBuilder, string $tableName): bool + { + // find table name in a structure: ['fromAlias' => [['joinTable' => ''], ...]] + $joinedParts = $queryBuilder->getQueryPart('join'); + foreach ($joinedParts as $joinedTables) { + foreach ($joinedTables as $join) { + if ($join['joinTable'] === $tableName) { + return true; + } + } + } + + return false; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php new file mode 100644 index 0000000000..cdcc584ed8 --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php @@ -0,0 +1,84 @@ +criteria = $criteria; + $this->sortClauses = $sortClauses; + $this->offset = $offset; + $this->limit = $limit; + } + + public function addCriterion(CriterionInterface $criterion): void + { + $this->criteria[] = $criterion; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] + */ + public function getCriteria(): array + { + return $this->criteria; + } + + public function addSortClause(SortClause $sortClause): void + { + $this->sortClauses[] = $sortClause; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause[] + */ + public function getSortClauses(): array + { + return $this->sortClauses; + } + + public function getOffset(): int + { + return $this->offset; + } + + public function setOffset(int $offset): void + { + $this->offset = $offset; + } + + public function getLimit(): int + { + return $this->limit; + } + + public function setLimit(int $limit): void + { + $this->limit = $limit; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php new file mode 100644 index 0000000000..234490fe3b --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php @@ -0,0 +1,41 @@ + */ + private array $value; + + /** + * @param list $value + */ + public function __construct(array $value) + { + $this->value = $value; + } + + /** + * @return list + */ + public function getValue(): array + { + return $this->value; + } + + /** + * @param list $value + */ + public function setValue(array $value): void + { + $this->value = $value; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php new file mode 100644 index 0000000000..12039eacee --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php @@ -0,0 +1,41 @@ + */ + private array $value; + + /** + * @param list $value + */ + public function __construct(array $value) + { + $this->value = $value; + } + + /** + * @return list + */ + public function getValue(): array + { + return $this->value; + } + + /** + * @param list $value + */ + public function setValue(array $value): void + { + $this->value = $value; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php new file mode 100644 index 0000000000..46b85fbf94 --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php @@ -0,0 +1,41 @@ + */ + private array $value; + + /** + * @param list $value + */ + public function __construct(array $value) + { + $this->value = $value; + } + + /** + * @return list + */ + public function getValue(): array + { + return $this->value; + } + + /** + * @param list $value + */ + public function setValue(array $value): void + { + $this->value = $value; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php new file mode 100644 index 0000000000..d8a6fb20ce --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php @@ -0,0 +1,41 @@ + */ + private array $value; + + /** + * @param list $value + */ + public function __construct(array $value) + { + $this->value = $value; + } + + /** + * @return list + */ + public function getValue(): array + { + return $this->value; + } + + /** + * @param list $value + */ + public function setValue(array $value): void + { + $this->value = $value; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php new file mode 100644 index 0000000000..6e4af0d9c5 --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php @@ -0,0 +1,31 @@ +value = $value; + } + + public function getValue(): bool + { + return $this->value; + } + + public function setValue(bool $value): void + { + $this->value = $value; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalAnd.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalAnd.php new file mode 100644 index 0000000000..333646fef2 --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalAnd.php @@ -0,0 +1,13 @@ + + */ + private array $criteria = []; + + /** + * @param list<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface> $criteria + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidCriterionArgumentException + */ + public function __construct(array $criteria) + { + foreach ($criteria as $key => $criterion) { + if (!$criterion instanceof CriterionInterface) { + throw new InvalidCriterionArgumentException($key, $criterion, CriterionInterface::class); + } + + $this->criteria[] = $criterion; + } + } + + /** + * @return list<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface> + */ + public function getCriteria(): array + { + return $this->criteria; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalOr.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalOr.php new file mode 100644 index 0000000000..dabb4a763a --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/LogicalOr.php @@ -0,0 +1,13 @@ +direction = $sortDirection; + $this->target = $sortTarget; + } +} diff --git a/src/contracts/Repository/Values/ContentType/Query/SortClause/Id.php b/src/contracts/Repository/Values/ContentType/Query/SortClause/Id.php new file mode 100644 index 0000000000..11259b2dec --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/Query/SortClause/Id.php @@ -0,0 +1,19 @@ +persistenceHandler->contentTypeHandler()->countContentTypes($query); + } + + public function findContentTypes(?ContentTypeQuery $query = null): array + { + return $this->persistenceHandler->contentTypeHandler()->findContentTypes($query); + } + public function loadContentTypeList(array $contentTypeIds): array { return $this->getMultipleCacheValues( diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway.php b/src/lib/Persistence/Legacy/Content/Type/Gateway.php index f5e9b28a13..74e80462cc 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway.php @@ -12,6 +12,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\FieldDefinition; use Ibexa\Contracts\Core\Persistence\Content\Type\Group; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Core\Persistence\Legacy\Content\StorageFieldDefinition; /** @@ -171,6 +172,13 @@ abstract public function removeFieldDefinitionTranslation( * Remove items created or modified by User. */ abstract public function removeByUserAndVersion(int $userId, int $version): void; + + abstract public function countContentTypes(?ContentTypeQuery $query = null): int; + + /** + * @return array> + */ + abstract public function findContentTypes(?ContentTypeQuery $query = null): array; } class_alias(Gateway::class, 'eZ\Publish\Core\Persistence\Legacy\Content\Type\Gateway'); diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php new file mode 100644 index 0000000000..e613393cf3 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php @@ -0,0 +1,36 @@ + + */ +final class ContainsFieldDefinitionIds extends Base +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof ContainsFieldDefinitionIdsCriterion; + } + + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $qb->andWhere( + $qb->expr()->in( + 'a.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ), + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php new file mode 100644 index 0000000000..50601079bf --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php @@ -0,0 +1,36 @@ + + */ +final class ContentTypeGroupIds extends Base +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof ContentTypeGroupIdsCriterion; + } + + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $qb->andWhere( + $qb->expr()->in( + 'ezcontentclass_classgroup_group_id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ), + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php new file mode 100644 index 0000000000..9aa907e4ba --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php @@ -0,0 +1,36 @@ + + */ +final class Identifiers extends Base +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IdentifiersCriterion; + } + + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $qb->andWhere( + $qb->expr()->in( + 'c.identifier', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + ), + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php new file mode 100644 index 0000000000..00f0c13741 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php @@ -0,0 +1,36 @@ + + */ +final class Ids extends Base +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IdsCriterion; + } + + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $qb->andWhere( + $qb->expr()->in( + 'c.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ), + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php new file mode 100644 index 0000000000..9f5c78a42a --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php @@ -0,0 +1,38 @@ + + */ +final class IsSystem extends Base +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IsSystemCriterion; + } + + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $this->joinContentTypeGroup($qb); + + $qb->andWhere( + $qb->expr()->eq( + 'ctg.is_system', + $qb->createNamedParameter(1, ParameterType::INTEGER) + ) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php new file mode 100644 index 0000000000..14b58e9010 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php @@ -0,0 +1,55 @@ + + */ +final class LogicalAnd extends Base +{ + private CriterionVisitor $criterionVisitor; + + public function __construct( + CriterionVisitor $criterionVisitor + ) { + $this->criterionVisitor = $criterionVisitor; + } + + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalAndCriterion; + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException + */ + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $constraints = []; + /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion */ + foreach ($criterion->getCriteria() as $criterion) { + $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); + if (null !== $constraint) { + $constraints[] = $constraint; + } + } + + if (empty($constraints)) { + return; + } + + $qb->andWhere($qb->expr()->and(...$constraints)); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php new file mode 100644 index 0000000000..10f7116453 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php @@ -0,0 +1,55 @@ + + */ +final class LogicalNot extends Base +{ + private CriterionVisitor $criterionVisitor; + + public function __construct( + CriterionVisitor $criterionVisitor + ) { + $this->criterionVisitor = $criterionVisitor; + } + + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalNotCriterion; + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException + */ + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $constraints = []; + /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot $criterion */ + foreach ($criterion->getCriteria() as $criterion) { + $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); + if (null !== $constraint) { + $constraints[] = $constraint; + } + } + + if (empty($constraints)) { + return; + } + + $qb->andWhere($qb->expr()->orX(...$constraints)); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php new file mode 100644 index 0000000000..bb2e05b81b --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php @@ -0,0 +1,55 @@ + + */ +final class LogicalOr extends Base +{ + private CriterionVisitor $criterionVisitor; + + public function __construct( + CriterionVisitor $criterionVisitor + ) { + $this->criterionVisitor = $criterionVisitor; + } + + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalOrCriterion; + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException + */ + public function apply(QueryBuilder $qb, CriterionInterface $criterion): void + { + $constraints = []; + /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr $criterion */ + foreach ($criterion->getCriteria() as $criterion) { + $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); + if (null !== $constraint) { + $constraints[] = $constraint; + } + } + + if (empty($constraints)) { + return; + } + + $qb->andWhere($qb->expr()->or(...$constraints)); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php new file mode 100644 index 0000000000..ff43aa030a --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php @@ -0,0 +1,37 @@ + + */ +final class ContainsFieldDefinitionIds implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof ContainsFieldDefinitionIdsCriterion; + } + + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + return $qb->expr()->in( + 'a.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php new file mode 100644 index 0000000000..0fdab5dc45 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php @@ -0,0 +1,37 @@ + + */ +final class ContentTypeGroupIds implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof ContentTypeGroupIdsCriterion; + } + + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + return $qb->expr()->in( + 'ezcontentclass_classgroup_group_id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php new file mode 100644 index 0000000000..3d5f21075a --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php @@ -0,0 +1,35 @@ + + */ +final class Identifiers implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IdentifiersCriterion; + } + + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + return $qb->expr()->in( + 'c.identifier', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php new file mode 100644 index 0000000000..67b59ac9e3 --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php @@ -0,0 +1,37 @@ + + */ +final class Ids implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IdsCriterion; + } + + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + return $qb->expr()->in( + 'c.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php new file mode 100644 index 0000000000..76f883994c --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php @@ -0,0 +1,37 @@ + + */ +final class IsSystem implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof IsSystemCriterion; + } + + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + return $qb->expr()->eq( + 'ezcontentclass_classgroup_is_system', + $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php new file mode 100644 index 0000000000..c2d735b95d --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php @@ -0,0 +1,42 @@ + + */ +final class LogicalAnd implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalAndCriterion; + } + + /** + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|string + */ + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ) { + $subexpressions = []; + /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion */ + foreach ($criterion->getCriteria() as $subCriterion) { + $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); + } + + return $qb->expr()->and(...$subexpressions); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php new file mode 100644 index 0000000000..e70c50a78a --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php @@ -0,0 +1,54 @@ + + */ + private array $criterionQueryBuilders; + + public function __construct(iterable $criterionQueryBuilders) + { + $this->criterionQueryBuilders = iterator_to_array($criterionQueryBuilders); + } + + /** + * @return \Doctrine\Common\Collections\Expr\CompositeExpression|string + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException if there's no builder for a criterion + */ + public function visitCriteria( + QueryBuilder $queryBuilder, + CriterionInterface $criterion + ) { + foreach ($this->criterionQueryBuilders as $criterionQueryBuilder) { + if ($criterionQueryBuilder->supports($criterion)) { + return $criterionQueryBuilder->buildQueryConstraint( + $this, + $queryBuilder, + $criterion + ); + } + } + + throw new NotImplementedException( + sprintf( + 'There is no Criterion Query Builder for %s Criterion', + get_class($criterion) + ) + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index 83b40160f6..bddcc96939 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -17,12 +17,17 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\FieldDefinition; use Ibexa\Contracts\Core\Persistence\Content\Type\Group; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause; +use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Base\Exceptions\NotFoundException; use Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator; use Ibexa\Core\Persistence\Legacy\Content\MultilingualStorageFieldDefinition; use Ibexa\Core\Persistence\Legacy\Content\StorageFieldDefinition; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway; use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway as SharedGateway; +use function Ibexa\PolyfillPhp82\iterator_to_array; use function sprintf; /** @@ -34,6 +39,11 @@ */ final class DoctrineDatabase extends Gateway { + private const SORT_DIRECTION_MAP = [ + SortClause::SORT_ASC => 'ASC', + SortClause::SORT_DESC => 'DESC', + ]; + /** * Columns of database tables. * @@ -114,17 +124,26 @@ final class DoctrineDatabase extends Gateway private $languageMaskGenerator; /** + * @var array + */ + private array $criterionHandlers; + + /** + * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface> $criterionHandlers + * * @throws \Doctrine\DBAL\DBALException */ public function __construct( Connection $connection, SharedGateway $sharedGateway, - MaskGenerator $languageMaskGenerator + MaskGenerator $languageMaskGenerator, + iterable $criterionHandlers ) { $this->connection = $connection; $this->dbPlatform = $connection->getDatabasePlatform(); $this->sharedGateway = $sharedGateway; $this->languageMaskGenerator = $languageMaskGenerator; + $this->criterionHandlers = iterator_to_array($criterionHandlers); } public function insertGroup(Group $group): int @@ -1405,6 +1424,93 @@ public function removeByUserAndVersion(int $userId, int $version): void } } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + $queryBuilder = $this->connection->createQueryBuilder(); + $queryBuilder + ->select('COUNT(ct.id)') + ->from(self::CONTENT_TYPE_TABLE, 'ct'); + + if ($query !== null && !empty($query->getCriteria())) { + $this->applyFilters($queryBuilder, $query->getCriteria()); + } + + return (int)$queryBuilder->execute()->fetchOne(); + } + + public function findContentTypes(?ContentTypeQuery $query = null): array + { + $queryBuilder = $this->getLoadTypeQueryBuilder(); + $queryBuilder->setMaxResults(25); + + if ($query === null) { + return $queryBuilder->execute()->fetchAllAssociative(); + } + + if (!empty($query->getCriteria())) { + $this->applyFilters($queryBuilder, $query->getCriteria()); + } + + if ($query->getOffset() > 0) { + $queryBuilder->setFirstResult($query->getOffset()); + } + + if ($query->getLimit() > 0) { + $queryBuilder->setMaxResults($query->getLimit()); + } + + foreach ($query->getSortClauses() as $sortClause) { + $column = sprintf('c.%s', $sortClause->target); + $queryBuilder->addOrderBy($column, $this->getQuerySortingDirection($sortClause->direction)); + } + + return $queryBuilder->execute()->fetchAllAssociative(); + } + + /** + * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException + */ + private function getQuerySortingDirection(string $direction): string + { + if (!isset(self::SORT_DIRECTION_MAP[$direction])) { + throw new InvalidArgumentException( + '$sortClause->direction', + sprintf( + 'Unsupported "%s" sorting directions, use one of the SortClause::SORT_* constants instead', + $direction + ) + ); + } + + return self::SORT_DIRECTION_MAP[$direction]; + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] $criteria + */ + private function applyFilters(QueryBuilder $qb, array $criteria): void + { + foreach ($criteria as $criterion) { + $this->applyCriterion($qb, $criterion); + } + } + + private function applyCriterion(QueryBuilder $qb, CriterionInterface $criterion): void + { + foreach ($this->criterionHandlers as $handler) { + if ($handler->supports($criterion)) { + $handler->apply($qb, $criterion); + + return; + } + } + + throw new InvalidArgumentException( + get_class($criterion), + 'No handler found for criterion of type. Make sure the handler service is registered and tagged with "ibexa.content_type.criterion_handler".' + ); + } + /** * @throws \Doctrine\DBAL\DBALException */ diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php index fd6229651d..aedca31ecd 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\FieldDefinition; use Ibexa\Contracts\Core\Persistence\Content\Type\Group; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Core\Base\Exceptions\DatabaseException; use Ibexa\Core\Persistence\Legacy\Content\StorageFieldDefinition; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway; @@ -341,6 +342,24 @@ public function removeByUserAndVersion(int $userId, int $version): void throw DatabaseException::wrap($e); } } + + public function countContentTypes(?ContentTypeQuery $query = null): int + { + try { + return $this->innerGateway->countContentTypes($query); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + + public function findContentTypes(?ContentTypeQuery $query = null): array + { + try { + return $this->innerGateway->findContentTypes($query); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } } class_alias(ExceptionConversion::class, 'eZ\Publish\Core\Persistence\Legacy\Content\Type\Gateway\ExceptionConversion'); diff --git a/src/lib/Persistence/Legacy/Content/Type/Handler.php b/src/lib/Persistence/Legacy/Content/Type/Handler.php index 88abfb59ef..b0d9a4b221 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Type/Handler.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as BaseContentTypeHandler; use Ibexa\Contracts\Core\Persistence\Content\Type\UpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Core\Base\Exceptions\BadStateException; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Base\Exceptions\NotFoundException; @@ -195,6 +196,22 @@ public function loadContentTypeList(array $contentTypeIds): array ); } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + return $this->contentTypeGateway->countContentTypes($query); + } + + /** + * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] + */ + public function findContentTypes(?ContentTypeQuery $query = null): array + { + return $this->mapper->extractTypesFromRows( + $this->contentTypeGateway->findContentTypes($query), + true + ); + } + /** * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] */ diff --git a/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php b/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php index ec9c7e990d..2c5d948bc4 100644 --- a/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php +++ b/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as BaseContentTypeHandler; use Ibexa\Contracts\Core\Persistence\Content\Type\UpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface; use Ibexa\Core\Persistence\Cache\InMemory\InMemoryCache; @@ -171,6 +172,16 @@ public function loadContentTypes($groupId, $status = Type::STATUS_DEFINED): arra return $types; } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + return $this->innerHandler->countContentTypes($query); + } + + public function findContentTypes(?ContentTypeQuery $query = null): array + { + return $this->innerHandler->findContentTypes($query); + } + /** * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] */ diff --git a/src/lib/Repository/ContentTypeService.php b/src/lib/Repository/ContentTypeService.php index 8476c6f021..34de51fa0a 100644 --- a/src/lib/Repository/ContentTypeService.php +++ b/src/lib/Repository/ContentTypeService.php @@ -34,6 +34,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition as APIFieldDefinition; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Contracts\Core\Repository\Values\User\User; use Ibexa\Core\Base\Exceptions\BadStateException; use Ibexa\Core\Base\Exceptions\ContentTypeFieldDefinitionValidationException; @@ -917,14 +918,10 @@ public function loadContentTypeDraft(int $contentTypeId, bool $ignoreOwnership = return $this->contentTypeDomainMapper->buildContentTypeDraftDomainObject($spiContentType); } - /** - * {@inheritdoc} - */ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLanguages = []): iterable { $spiContentTypes = $this->contentTypeHandler->loadContentTypeList($contentTypeIds); $contentTypes = []; - // @todo We could bulk load content type group proxies involved in the future & pass those relevant per type to mapper foreach ($spiContentTypes as $spiContentType) { $contentTypes[$spiContentType->id] = $this->contentTypeDomainMapper->buildContentTypeDomainObject( @@ -936,6 +933,21 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan return $contentTypes; } + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + { + $contentTypes = []; + $spiContentTypes = $this->contentTypeHandler->findContentTypes($query); + + foreach ($spiContentTypes as $spiContentType) { + $contentTypes[] = $this->contentTypeDomainMapper->buildContentTypeDomainObject( + $spiContentType, + $prioritizedLanguages + ); + } + + return $contentTypes; + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/ContentTypeService.php b/src/lib/Repository/SiteAccessAware/ContentTypeService.php index 939c03f652..665024a275 100644 --- a/src/lib/Repository/SiteAccessAware/ContentTypeService.php +++ b/src/lib/Repository/SiteAccessAware/ContentTypeService.php @@ -18,6 +18,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinition; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Contracts\Core\Repository\Values\User\User; /** @@ -119,6 +120,11 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan return $this->service->loadContentTypeList($contentTypeIds, $prioritizedLanguages); } + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + { + return $this->service->findContentTypes($query, $prioritizedLanguages); + } + public function loadContentTypes(ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = null): iterable { $prioritizedLanguages = $this->languageResolver->getPrioritizedLanguages($prioritizedLanguages); diff --git a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml index a4c2d40a77..8af1b1dfa3 100644 --- a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml +++ b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml @@ -1,10 +1,52 @@ services: + _instanceof: + Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface: + tags: [ 'ibexa.content_type.criterion_handler' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\ContainsFieldDefinitionIds: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\ContentTypeGroupIds: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\Identifiers: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\Ids: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\IsSystem: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalAnd: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor: + arguments: + $criterionQueryBuilders: !tagged_iterator 'ibexa.content_type.criterion_query_builder' + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\: + resource: '../../../../Persistence/Legacy/Content/Type/Gateway/CriterionHandler/*' + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalAnd: + arguments: + $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalOr: + arguments: + $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalNot: + arguments: + $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\DoctrineDatabase.inner: class: Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\DoctrineDatabase arguments: - - '@ibexa.api.storage_engine.legacy.connection' - - '@Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway' - - '@Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator' + $connection: '@ibexa.api.storage_engine.legacy.connection' + $sharedGateway: '@Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway' + $languageMaskGenerator: '@Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator' + $criterionHandlers: !tagged_iterator 'ibexa.content_type.criterion_handler' Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\ExceptionConversion: class: Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\ExceptionConversion diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php new file mode 100644 index 0000000000..7477fb5794 --- /dev/null +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -0,0 +1,183 @@ +findContentTypes(); + + self::assertCount(25, $contentTypes); + } + + public function testFindContentTypesWithIdentifiersCriterion(): void + { + $contentTypeService = self::getContentTypeService(); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new Identifiers(['folder', 'article']), + ]) + ); + + self::assertCount(2, $contentTypes); + + $contentTypesGroupedByIdentifier = []; + foreach ($contentTypes as $contentType) { + $contentTypesGroupedByIdentifier[$contentType->identifier] = $contentType; + } + + self::assertSame('folder', $contentTypesGroupedByIdentifier['folder']->identifier); + self::assertSame('article', $contentTypesGroupedByIdentifier['article']->identifier); + } + + public function testFindContentTypesContainingFieldDefinitions(): void + { + $contentTypeService = self::getContentTypeService(); + $folderContentType = $contentTypeService->loadContentTypeByIdentifier('folder'); + + $fieldDefinitionToInclude = null; + foreach ($folderContentType->getFieldDefinitions() as $fieldDefinition) { + if ($fieldDefinition->getIdentifier() === 'short_name') { + $fieldDefinitionToInclude = $fieldDefinition; + } + } + + assert($fieldDefinitionToInclude !== null); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new ContainsFieldDefinitionIds([$fieldDefinitionToInclude->getId()]), + ]) + ); + + self::assertCount(1, $contentTypes); + self::assertSame('folder', $contentTypes[0]->getIdentifier()); + } + + public function testFindContentTypesByGroupIdentifiers(): void + { + $contentTypeService = self::getContentTypeService(); + $usersContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Users'); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new ContentTypeGroupIds([$usersContentTypeGroup->id]), + ]) + ); + + $contentTypesGroupedByIdentifier = []; + foreach ($contentTypes as $contentType) { + $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; + } + + self::assertCount(2, $contentTypes); + self::assertSame('user', $contentTypesGroupedByIdentifier['user']->getIdentifier()); + self::assertSame('user_group', $contentTypesGroupedByIdentifier['user_group']->getIdentifier()); + } + + public function testFindContentTypesSystemGroup(): void + { + $contentTypeService = self::getContentTypeService(); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new IsSystem(false), + ]) + ); + + foreach ($contentTypes as $contentType) { + $group = $contentType->getContentTypeGroups()[0]; + self::assertFalse($group->isSystem); + } + } + + public function testFindContentTypesLogicalAnd(): void + { + $contentTypeService = self::getContentTypeService(); + $contentContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Content'); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new LogicalAnd([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([$contentContentTypeGroup->id]), + ]), + ]) + ); + + $contentTypesGroupedByIdentifier = []; + foreach ($contentTypes as $contentType) { + $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; + } + + self::assertCount(2, $contentTypes); + self::assertEqualsCanonicalizing(['folder', 'article'], array_keys($contentTypesGroupedByIdentifier)); + } + + public function testFindContentTypesWithLogicalOrContainingLogicalAnd(): void + { + $contentTypeService = self::getContentTypeService(); + $contentContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Content'); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new LogicalOr([ + new LogicalAnd([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([$contentContentTypeGroup->id]), + ]), + new Identifiers(['user']), + ]), + ]) + ); + + $contentTypesGroupedByIdentifier = []; + foreach ($contentTypes as $contentType) { + $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; + } + + self::assertCount(3, $contentTypes); + self::assertEqualsCanonicalizing(['folder', 'article', 'user'], array_keys($contentTypesGroupedByIdentifier)); + } + + public function testFindContentTypesAscSortedByIdentifier(): void + { + $contentTypeService = self::getContentTypeService(); + + $contentTypes = $contentTypeService->findContentTypes( + new ContentTypeQuery([ + new Identifiers(['folder', 'article', 'user', 'file']), + ]) + ); + + $contentTypesGroupedByIdentifier = []; + foreach ($contentTypes as $contentType) { + $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; + } + + self::assertCount(4, $contentTypes); + self::assertSame(['article', 'file', 'folder', 'user'], array_keys($contentTypesGroupedByIdentifier)); + } +} diff --git a/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php b/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php index f06999a73e..ea36273ff4 100644 --- a/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php +++ b/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php @@ -1153,7 +1153,8 @@ protected function getGateway(): DoctrineDatabase $this->gateway = new DoctrineDatabase( $this->getDatabaseConnection(), $this->getSharedGateway(), - $this->getLanguageMaskGenerator() + $this->getLanguageMaskGenerator(), + [], ); } diff --git a/tests/lib/Search/Legacy/Content/AbstractTestCase.php b/tests/lib/Search/Legacy/Content/AbstractTestCase.php index 0ae8a3bf37..dbdd2f17da 100644 --- a/tests/lib/Search/Legacy/Content/AbstractTestCase.php +++ b/tests/lib/Search/Legacy/Content/AbstractTestCase.php @@ -89,6 +89,7 @@ protected function getContentTypeHandler(): SPIContentTypeHandler $this->getDatabaseConnection(), $this->getSharedGateway(), $this->getLanguageMaskGenerator(), + [] ), new ContentTypeMapper( $this->getConverterRegistry(), From e2d3b62d1eb02df4d8faa38d681486a868b83798 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Aug 2025 16:40:20 +0200 Subject: [PATCH 02/14] IBX-10458: PHPStan --- .../Repository/Values/ContentType/Query/Base.php | 3 +++ .../CriterionHandler/ContainsFieldDefinitionIds.php | 6 +++--- .../Type/Gateway/CriterionHandler/ContentTypeGroupIds.php | 6 +++--- .../Content/Type/Gateway/CriterionHandler/Identifiers.php | 6 +++--- .../Legacy/Content/Type/Gateway/CriterionHandler/Ids.php | 6 +++--- .../Content/Type/Gateway/CriterionHandler/IsSystem.php | 6 +++--- .../Content/Type/Gateway/CriterionHandler/LogicalAnd.php | 5 ++--- .../Content/Type/Gateway/CriterionHandler/LogicalNot.php | 5 ++--- .../Content/Type/Gateway/CriterionHandler/LogicalOr.php | 5 ++--- .../CriterionQueryBuilder/ContainsFieldDefinitionIds.php | 7 +++++-- .../Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php | 7 +++++-- .../Type/Gateway/CriterionQueryBuilder/Identifiers.php | 7 +++++-- .../Content/Type/Gateway/CriterionQueryBuilder/Ids.php | 7 +++++-- .../Type/Gateway/CriterionQueryBuilder/IsSystem.php | 7 +++++-- .../Type/Gateway/CriterionQueryBuilder/LogicalAnd.php | 8 ++++---- .../Type/Gateway/CriterionVisitor/CriterionVisitor.php | 7 +++++-- .../Legacy/Content/Type/Gateway/DoctrineDatabase.php | 4 ++-- 17 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/contracts/Repository/Values/ContentType/Query/Base.php b/src/contracts/Repository/Values/ContentType/Query/Base.php index 763eb2476f..86a18af7eb 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Base.php +++ b/src/contracts/Repository/Values/ContentType/Query/Base.php @@ -11,6 +11,9 @@ use Doctrine\DBAL\Query\QueryBuilder; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway; +/** + * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface> + */ abstract class Base implements CriterionHandlerInterface { /** diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php index e613393cf3..001d176538 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds as ContainsFieldDefinitionIdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds> - */ final class ContainsFieldDefinitionIds extends Base { public function supports(CriterionInterface $criterion): bool @@ -24,6 +21,9 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof ContainsFieldDefinitionIdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds $criterion + */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void { $qb->andWhere( diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php index 50601079bf..50d88d4b3f 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds as ContentTypeGroupIdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds> - */ final class ContentTypeGroupIds extends Base { public function supports(CriterionInterface $criterion): bool @@ -24,6 +21,9 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof ContentTypeGroupIdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds $criterion + */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void { $qb->andWhere( diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php index 9aa907e4ba..8efbb9a4bc 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers as IdentifiersCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers> - */ final class Identifiers extends Base { public function supports(CriterionInterface $criterion): bool @@ -24,6 +21,9 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IdentifiersCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers $criterion + */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void { $qb->andWhere( diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php index 00f0c13741..094d8edfbf 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids as IdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids> - */ final class Ids extends Base { public function supports(CriterionInterface $criterion): bool @@ -24,6 +21,9 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids $criterion + */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void { $qb->andWhere( diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php index 9f5c78a42a..1cc5130d3e 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem as IsSystemCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem> - */ final class IsSystem extends Base { public function supports(CriterionInterface $criterion): bool @@ -24,6 +21,9 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IsSystemCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem $criterion + */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void { $this->joinContentTypeGroup($qb); diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php index 14b58e9010..15ccc86dc1 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd> - */ final class LogicalAnd extends Base { private CriterionVisitor $criterionVisitor; @@ -33,6 +30,8 @@ public function supports(CriterionInterface $criterion): bool } /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion + * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php index 10f7116453..408be9fb19 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot> - */ final class LogicalNot extends Base { private CriterionVisitor $criterionVisitor; @@ -33,6 +30,8 @@ public function supports(CriterionInterface $criterion): bool } /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot $criterion + * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php index bb2e05b81b..272a357185 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php @@ -14,9 +14,6 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; -/** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr> - */ final class LogicalOr extends Base { private CriterionVisitor $criterionVisitor; @@ -33,6 +30,8 @@ public function supports(CriterionInterface $criterion): bool } /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr $criterion + * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php index ff43aa030a..0238b2b99a 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php @@ -15,7 +15,7 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds> */ final class ContainsFieldDefinitionIds implements CriterionQueryBuilderInterface { @@ -24,11 +24,14 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof ContainsFieldDefinitionIdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds $criterion + */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): string { return $qb->expr()->in( 'a.id', $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php index 0fdab5dc45..627887982c 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php @@ -15,7 +15,7 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds> */ final class ContentTypeGroupIds implements CriterionQueryBuilderInterface { @@ -24,11 +24,14 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof ContentTypeGroupIdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds $criterion + */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): string { return $qb->expr()->in( 'ezcontentclass_classgroup_group_id', $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php index 0f90a741b3..c5d75575a2 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php @@ -15,7 +15,7 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers> */ final class Identifiers implements CriterionQueryBuilderInterface { @@ -24,11 +24,14 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IdentifiersCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers $criterion + */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): string { return $qb->expr()->in( 'c.identifier', $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php index 67b59ac9e3..aa28f49659 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php @@ -15,7 +15,7 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids> */ final class Ids implements CriterionQueryBuilderInterface { @@ -24,11 +24,14 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IdsCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids $criterion + */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): string { return $qb->expr()->in( 'c.id', $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php index 76f883994c..3fab80a3c8 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php @@ -15,7 +15,7 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem> */ final class IsSystem implements CriterionQueryBuilderInterface { @@ -24,11 +24,14 @@ public function supports(CriterionInterface $criterion): bool return $criterion instanceof IsSystemCriterion; } + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem $criterion + */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): string { return $qb->expr()->eq( 'ezcontentclass_classgroup_is_system', $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php index c2d735b95d..d546647224 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php @@ -8,13 +8,14 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder; +use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd as LogicalAndCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd> + * @implements \Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd> */ final class LogicalAnd implements CriterionQueryBuilderInterface { @@ -24,15 +25,14 @@ public function supports(CriterionInterface $criterion): bool } /** - * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|string + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion */ public function buildQueryConstraint( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion - ) { + ): CompositeExpression { $subexpressions = []; - /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion */ foreach ($criterion->getCriteria() as $subCriterion) { $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php index e70c50a78a..14767c77f6 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php @@ -16,17 +16,20 @@ final class CriterionVisitor { /** - * @var array<\Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface> + * @var array<\Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> */ private array $criterionQueryBuilders; + /** + * @param iterable<\Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionQueryBuilders + */ public function __construct(iterable $criterionQueryBuilders) { $this->criterionQueryBuilders = iterator_to_array($criterionQueryBuilders); } /** - * @return \Doctrine\Common\Collections\Expr\CompositeExpression|string + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|string * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException if there's no builder for a criterion */ diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index bddcc96939..0bae9fc3db 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -124,12 +124,12 @@ final class DoctrineDatabase extends Gateway private $languageMaskGenerator; /** - * @var array + * @var array> */ private array $criterionHandlers; /** - * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface> $criterionHandlers + * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionHandlers * * @throws \Doctrine\DBAL\DBALException */ From a117dddb3214ce2c8af391db73b66e7fbb62cdaf Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Tue, 12 Aug 2025 22:29:24 +0200 Subject: [PATCH 03/14] IBX-10458: Update --- .../Repository/ContentTypeService.php | 2 + .../Decorator/ContentTypeServiceDecorator.php | 5 + .../Query/Criterion/ContentTypeGroupIds.php | 8 +- .../Gateway/CriterionHandler/IsSystem.php | 2 +- .../Gateway/CriterionQueryBuilder/Ids.php | 2 +- .../CriterionQueryBuilder/IsSystem.php | 2 +- .../CriterionQueryBuilder/LogicalNot.php | 43 ++++ .../CriterionQueryBuilder/LogicalOr.php | 44 +++++ src/lib/Repository/ContentTypeService.php | 11 +- .../SiteAccessAware/ContentTypeService.php | 5 + .../storage_engines/legacy/content_type.yml | 6 + .../CountContentTypesTest.php | 139 +++++++++++++ .../FindContentTypesTest.php | 187 +++++++++--------- .../ContentTypeServiceTest.php | 5 + 14 files changed, 360 insertions(+), 101 deletions(-) create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php create mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php create mode 100644 tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php diff --git a/src/contracts/Repository/ContentTypeService.php b/src/contracts/Repository/ContentTypeService.php index 0bf80b9434..c85e87951c 100644 --- a/src/contracts/Repository/ContentTypeService.php +++ b/src/contracts/Repository/ContentTypeService.php @@ -181,6 +181,8 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan */ public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array; + public function countContentTypes(?ContentTypeQuery $query = null): int; + /** * Get content type objects which belong to the given content type group. * diff --git a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php index a1f59e05ee..24bd79c01f 100644 --- a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php +++ b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php @@ -113,6 +113,11 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit return $this->innerService->findContentTypes($query, $prioritizedLanguages); } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + return $this->innerService->countContentTypes($query); + } + public function loadContentTypes( ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = [] diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php index 12039eacee..e3d4a622dc 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php @@ -12,11 +12,11 @@ final class ContentTypeGroupIds implements CriterionInterface { - /** @var list */ + /** @var list */ private array $value; /** - * @param list $value + * @param list $value */ public function __construct(array $value) { @@ -24,7 +24,7 @@ public function __construct(array $value) } /** - * @return list + * @return list */ public function getValue(): array { @@ -32,7 +32,7 @@ public function getValue(): array } /** - * @param list $value + * @param list $value */ public function setValue(array $value): void { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php index 1cc5130d3e..64f91ff7df 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php @@ -31,7 +31,7 @@ public function apply(QueryBuilder $qb, CriterionInterface $criterion): void $qb->andWhere( $qb->expr()->eq( 'ctg.is_system', - $qb->createNamedParameter(1, ParameterType::INTEGER) + $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) ) ); } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php index aa28f49659..a474283827 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php @@ -34,7 +34,7 @@ public function buildQueryConstraint( ): string { return $qb->expr()->in( 'c.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php index 3fab80a3c8..793183c159 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php @@ -33,7 +33,7 @@ public function buildQueryConstraint( CriterionInterface $criterion ): string { return $qb->expr()->eq( - 'ezcontentclass_classgroup_is_system', + 'ctg.is_system', $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) ); } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php new file mode 100644 index 0000000000..2f73bb15cd --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php @@ -0,0 +1,43 @@ + + */ +final class LogicalNot implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalNotCriterion; + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot $criterion + */ + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + if (empty($criterion->getCriteria())) { + return ''; + } + + return sprintf( + 'NOT (%s)', + $criterionVisitor->visitCriteria($qb, $criterion->getCriteria()[0]), + ); + } +} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php new file mode 100644 index 0000000000..a9f2501d8e --- /dev/null +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php @@ -0,0 +1,44 @@ + + */ +final class LogicalOr implements CriterionQueryBuilderInterface +{ + public function supports(CriterionInterface $criterion): bool + { + return $criterion instanceof LogicalOrCriterion; + } + + /** + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr $criterion + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException + */ + public function buildQueryConstraint( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): CompositeExpression { + $subexpressions = []; + foreach ($criterion->getCriteria() as $subCriterion) { + $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); + } + + return $qb->expr()->or(...$subexpressions); + } +} diff --git a/src/lib/Repository/ContentTypeService.php b/src/lib/Repository/ContentTypeService.php index 34de51fa0a..08d10d9192 100644 --- a/src/lib/Repository/ContentTypeService.php +++ b/src/lib/Repository/ContentTypeService.php @@ -936,11 +936,11 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array { $contentTypes = []; - $spiContentTypes = $this->contentTypeHandler->findContentTypes($query); + $persistenceContentTypes = $this->contentTypeHandler->findContentTypes($query); - foreach ($spiContentTypes as $spiContentType) { + foreach ($persistenceContentTypes as $persistenceContentType) { $contentTypes[] = $this->contentTypeDomainMapper->buildContentTypeDomainObject( - $spiContentType, + $persistenceContentType, $prioritizedLanguages ); } @@ -948,6 +948,11 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit return $contentTypes; } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + return $this->contentTypeHandler->countContentTypes($query); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/ContentTypeService.php b/src/lib/Repository/SiteAccessAware/ContentTypeService.php index 665024a275..054f3d22cf 100644 --- a/src/lib/Repository/SiteAccessAware/ContentTypeService.php +++ b/src/lib/Repository/SiteAccessAware/ContentTypeService.php @@ -125,6 +125,11 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit return $this->service->findContentTypes($query, $prioritizedLanguages); } + public function countContentTypes(?ContentTypeQuery $query = null): int + { + return $this->service->countContentTypes($query); + } + public function loadContentTypes(ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = null): iterable { $prioritizedLanguages = $this->languageResolver->getPrioritizedLanguages($prioritizedLanguages); diff --git a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml index 8af1b1dfa3..c077d6f0e4 100644 --- a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml +++ b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml @@ -21,6 +21,12 @@ services: Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalAnd: tags: [ 'ibexa.content_type.criterion_query_builder' ] + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalOr: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalNot: + tags: [ 'ibexa.content_type.criterion_query_builder' ] + Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor: arguments: $criterionQueryBuilders: !tagged_iterator 'ibexa.content_type.criterion_query_builder' diff --git a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php new file mode 100644 index 0000000000..06b28f886c --- /dev/null +++ b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php @@ -0,0 +1,139 @@ +countContentTypes(); + + $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery([], [], 0, 999)); + + self::assertSame(count($contentTypesObjects), $contentTypesCount); + } + + /** + * @dataProvider dataProviderForTestCount + */ + public function testCountContentTypes(ContentTypeQuery $query, int $expectedCount): void + { + $contentTypeService = self::getContentTypeService(); + + $count = $contentTypeService->countContentTypes($query); + + self::assertSame($expectedCount, $count); + } + + /** + * @return iterable + */ + public function dataProviderForTestCount(): iterable + { + yield 'identifiers' => [ + new ContentTypeQuery([ + new Identifiers(['folder', 'article']), + ]), + 2, + ]; + + yield 'user group content type' => [ + new ContentTypeQuery([ + new ContentTypeGroupIds([2]), + ]), + 2, + ]; + + yield 'ids' => [ + new ContentTypeQuery([ + new Ids([1]), + ]), + 1, + ]; + + yield 'system group' => [ + new ContentTypeQuery([ + new IsSystem(false), + ]), + 3, + ]; + + yield 'logical and' => [ + new ContentTypeQuery([ + new LogicalAnd([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([1]), + ]), + ]), + 2, + ]; + + yield 'logical or' => [ + new ContentTypeQuery([ + new LogicalOr([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([2]), + ]), + ]), + 4, + ]; + + yield 'logical not resulting in empty set' => [ + new ContentTypeQuery([ + new LogicalAnd([ + new LogicalNot([ + new Identifiers(['user', 'user_group']), + ]), + new ContentTypeGroupIds([2]), + ]), + ]), + 0, + ]; + + yield 'logical not' => [ + new ContentTypeQuery([ + new LogicalAnd([ + new LogicalNot([ + new Identifiers(['user']), + ]), + new ContentTypeGroupIds([2]), + ]), + ]), + 1, + ]; + + yield 'logical or outside with logical and inside' => [ + new ContentTypeQuery([ + new LogicalOr([ + new LogicalAnd([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([1]), + ]), + new Identifiers(['user']), + ]), + ]), + 3, + ]; + } +} diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index 7477fb5794..9a99d2c4c0 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -8,12 +8,15 @@ namespace Ibexa\Tests\Integration\Core\Repository\ContentTypeService; +use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr; use Ibexa\Tests\Integration\Core\RepositoryTestCase; @@ -31,25 +34,41 @@ public function testFindContentTypesWithNullQueryFinds25Results(): void self::assertCount(25, $contentTypes); } - public function testFindContentTypesWithIdentifiersCriterion(): void + /** + * @param list $expectedIdentifiers + * + * @dataProvider dataProviderForTestFindContentTypes + */ + public function testFindContentTypes(ContentTypeQuery $query, array $expectedIdentifiers): void + { + $contentTypeService = self::getContentTypeService(); + + $contentTypes = $contentTypeService->findContentTypes($query); + $identifiers = array_map( + static fn (ContentType $contentType): string => $contentType->getIdentifier(), + $contentTypes + ); + + self::assertCount(count($expectedIdentifiers), $identifiers); + self::assertEqualsCanonicalizing($expectedIdentifiers, $identifiers); + } + + public function testFindContentTypesAscSortedByIdentifier(): void { $contentTypeService = self::getContentTypeService(); $contentTypes = $contentTypeService->findContentTypes( new ContentTypeQuery([ - new Identifiers(['folder', 'article']), + new Identifiers(['folder', 'article', 'user', 'file']), ]) ); + $identifiers = array_map( + static fn (ContentType $contentType): string => $contentType->getIdentifier(), + $contentTypes + ); - self::assertCount(2, $contentTypes); - - $contentTypesGroupedByIdentifier = []; - foreach ($contentTypes as $contentType) { - $contentTypesGroupedByIdentifier[$contentType->identifier] = $contentType; - } - - self::assertSame('folder', $contentTypesGroupedByIdentifier['folder']->identifier); - self::assertSame('article', $contentTypesGroupedByIdentifier['article']->identifier); + self::assertCount(4, $identifiers); + self::assertSame(['article', 'file', 'folder', 'user'], $identifiers); } public function testFindContentTypesContainingFieldDefinitions(): void @@ -76,108 +95,94 @@ public function testFindContentTypesContainingFieldDefinitions(): void self::assertSame('folder', $contentTypes[0]->getIdentifier()); } - public function testFindContentTypesByGroupIdentifiers(): void + /** + * @return iterable}> + */ + public function dataProviderForTestFindContentTypes(): iterable { - $contentTypeService = self::getContentTypeService(); - $usersContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Users'); - - $contentTypes = $contentTypeService->findContentTypes( + yield 'identifiers' => [ new ContentTypeQuery([ - new ContentTypeGroupIds([$usersContentTypeGroup->id]), - ]) - ); - - $contentTypesGroupedByIdentifier = []; - foreach ($contentTypes as $contentType) { - $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; - } + new Identifiers(['folder', 'article']), + ]), + ['article', 'folder'], + ]; - self::assertCount(2, $contentTypes); - self::assertSame('user', $contentTypesGroupedByIdentifier['user']->getIdentifier()); - self::assertSame('user_group', $contentTypesGroupedByIdentifier['user_group']->getIdentifier()); - } + yield 'user group content type' => [ + new ContentTypeQuery([ + new ContentTypeGroupIds([2]), + ]), + ['user', 'user_group'], + ]; - public function testFindContentTypesSystemGroup(): void - { - $contentTypeService = self::getContentTypeService(); + yield 'ids' => [ + new ContentTypeQuery([ + new Ids([1]), + ]), + ['folder'], + ]; - $contentTypes = $contentTypeService->findContentTypes( + yield 'system group' => [ new ContentTypeQuery([ new IsSystem(false), - ]) - ); + ]), + ['folder', 'user', 'user_group'], + ]; - foreach ($contentTypes as $contentType) { - $group = $contentType->getContentTypeGroups()[0]; - self::assertFalse($group->isSystem); - } - } - - public function testFindContentTypesLogicalAnd(): void - { - $contentTypeService = self::getContentTypeService(); - $contentContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Content'); - - $contentTypes = $contentTypeService->findContentTypes( + yield 'logical and' => [ new ContentTypeQuery([ new LogicalAnd([ new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([$contentContentTypeGroup->id]), + new ContentTypeGroupIds([1]), ]), - ]) - ); + ]), + ['folder', 'article'], + ]; - $contentTypesGroupedByIdentifier = []; - foreach ($contentTypes as $contentType) { - $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; - } + yield 'logical or' => [ + new ContentTypeQuery([ + new LogicalOr([ + new Identifiers(['folder', 'article']), + new ContentTypeGroupIds([2]), + ]), + ]), + ['folder', 'article', 'user', 'user_group'], + ]; - self::assertCount(2, $contentTypes); - self::assertEqualsCanonicalizing(['folder', 'article'], array_keys($contentTypesGroupedByIdentifier)); - } + yield 'logical not resulting in empty set' => [ + new ContentTypeQuery([ + new LogicalAnd([ + new LogicalNot([ + new Identifiers(['user', 'user_group']), + ]), + new ContentTypeGroupIds([2]), + ]), + ]), + [], + ]; - public function testFindContentTypesWithLogicalOrContainingLogicalAnd(): void - { - $contentTypeService = self::getContentTypeService(); - $contentContentTypeGroup = $contentTypeService->loadContentTypeGroupByIdentifier('Content'); + yield 'logical not' => [ + new ContentTypeQuery([ + new LogicalAnd([ + new LogicalNot([ + new Identifiers(['user']), + ]), + new ContentTypeGroupIds([2]), + ]), + ]), + ['user_group'], + ]; - $contentTypes = $contentTypeService->findContentTypes( + yield 'logical or outside with logical and inside' => [ new ContentTypeQuery([ new LogicalOr([ new LogicalAnd([ new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([$contentContentTypeGroup->id]), + new ContentTypeGroupIds([1]), ]), new Identifiers(['user']), ]), - ]) - ); - - $contentTypesGroupedByIdentifier = []; - foreach ($contentTypes as $contentType) { - $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; - } - - self::assertCount(3, $contentTypes); - self::assertEqualsCanonicalizing(['folder', 'article', 'user'], array_keys($contentTypesGroupedByIdentifier)); - } - - public function testFindContentTypesAscSortedByIdentifier(): void - { - $contentTypeService = self::getContentTypeService(); - - $contentTypes = $contentTypeService->findContentTypes( - new ContentTypeQuery([ - new Identifiers(['folder', 'article', 'user', 'file']), - ]) - ); - - $contentTypesGroupedByIdentifier = []; - foreach ($contentTypes as $contentType) { - $contentTypesGroupedByIdentifier[$contentType->getIdentifier()] = $contentType; - } - - self::assertCount(4, $contentTypes); - self::assertSame(['article', 'file', 'folder', 'user'], array_keys($contentTypesGroupedByIdentifier)); + ]), + ['folder', 'article', 'user'], + ]; } } diff --git a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php index 5701ffb0db..7f433a42c4 100644 --- a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php +++ b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php @@ -12,6 +12,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentTypeUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; use Ibexa\Core\Repository\SiteAccessAware\ContentTypeService; use Ibexa\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\Repository\Values\ContentType\ContentTypeCreateStruct; @@ -99,6 +100,10 @@ public function providerForPassTroughMethods() ['removeContentTypeTranslation', [$contentTypeDraft, 'ger-DE'], $contentTypeDraft], ['deleteUserDrafts', [14], null], + + ['findContentTypes', [new ContentTypeQuery()], [$contentType]], + + ['countContentTypes', [new ContentTypeQuery()], 1], ]; } From f2ad9c9040b9d52e5ee5b4280bbfb91bb9e71e9b Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 10:29:44 +0200 Subject: [PATCH 04/14] IBX-10458: Refactor --- .../Values/ContentType/Query/Base.php | 43 ++++++++++++++++ .../ContentType/Query/ContentTypeQuery.php | 19 +++---- .../Query/CriterionHandlerInterface.php | 9 +++- .../ContainsFieldDefinitionIds.php | 18 ++++--- .../CriterionHandler/ContentTypeGroupIds.php | 18 ++++--- .../Gateway/CriterionHandler/Identifiers.php | 16 +++--- .../Type/Gateway/CriterionHandler/Ids.php | 16 +++--- .../Gateway/CriterionHandler/IsSystem.php | 17 ++++--- .../Gateway/CriterionHandler/LogicalAnd.php | 32 ++++-------- .../Gateway/CriterionHandler/LogicalNot.php | 33 ++++-------- .../Gateway/CriterionHandler/LogicalOr.php | 32 ++++-------- .../ContainsFieldDefinitionIds.php | 40 --------------- .../ContentTypeGroupIds.php | 40 --------------- .../CriterionQueryBuilderInterface.php | 35 ------------- .../CriterionQueryBuilder/Identifiers.php | 40 --------------- .../Gateway/CriterionQueryBuilder/Ids.php | 40 --------------- .../CriterionQueryBuilder/IsSystem.php | 40 --------------- .../CriterionQueryBuilder/LogicalAnd.php | 42 --------------- .../CriterionQueryBuilder/LogicalNot.php | 43 ---------------- .../CriterionQueryBuilder/LogicalOr.php | 44 ---------------- .../CriterionVisitor/CriterionVisitor.php | 18 +++---- .../Content/Type/Gateway/DoctrineDatabase.php | 51 ++++--------------- .../storage_engines/legacy/content_type.yml | 40 +-------------- .../CountContentTypesTest.php | 38 +++++++------- .../FindContentTypesTest.php | 44 ++++++++-------- .../Legacy/Content/LanguageAwareTestCase.php | 15 ++++++ .../Type/Gateway/DoctrineDatabaseTest.php | 2 +- .../Legacy/Content/AbstractTestCase.php | 2 +- 28 files changed, 217 insertions(+), 610 deletions(-) delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Identifiers.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php delete mode 100644 src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php diff --git a/src/contracts/Repository/Values/ContentType/Query/Base.php b/src/contracts/Repository/Values/ContentType/Query/Base.php index 86a18af7eb..729fd66992 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Base.php +++ b/src/contracts/Repository/Values/ContentType/Query/Base.php @@ -28,6 +28,49 @@ protected function joinContentTypeGroup(QueryBuilder $query): void 'ctg', 'g.contentclass_id = ctg.id' ); + $query->addSelect('ctg.id'); + } + } + + /** + * Inner join the `ezcontentclass_attribute` table if not joined yet. + */ + protected function joinFieldDefinitions(QueryBuilder $query): void + { + if (!$this->hasJoinedTable($query, Gateway::FIELD_DEFINITION_TABLE)) { + $expr = $query->expr(); + + $query->leftJoin( + 'c', + Gateway::FIELD_DEFINITION_TABLE, + 'a', + (string)$expr->and( + 'c.id = a.contentclass_id', + 'c.version = a.version' + ) + ); + $query->addSelect('a.id'); + } + } + + /** + * Inner join the `ezcontentclass_classgroup` table if not joined yet. + */ + protected function joinContentTypeGroupAssignmentTable(QueryBuilder $query): void + { + if (!$this->hasJoinedTable($query, Gateway::CONTENT_TYPE_TO_GROUP_ASSIGNMENT_TABLE)) { + $expr = $query->expr(); + + $query->leftJoin( + 'c', + Gateway::CONTENT_TYPE_TO_GROUP_ASSIGNMENT_TABLE, + 'g', + (string)$expr->and( + 'c.id = g.contentclass_id', + 'c.version = g.contentclass_version', + ) + ); + $query->addSelect('g.group_id'); } } diff --git a/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php index cdcc584ed8..5292bdc9d5 100644 --- a/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php +++ b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php @@ -10,8 +10,7 @@ final class ContentTypeQuery { - /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] */ - private array $criteria; + private ?CriterionInterface $criterion; /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause[] */ private array $sortClauses; @@ -21,32 +20,28 @@ final class ContentTypeQuery private int $limit; /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] $criteria * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause[] $sortClauses */ public function __construct( - array $criteria = [], + ?CriterionInterface $criterion = null, array $sortClauses = [], int $offset = 0, int $limit = 25 ) { - $this->criteria = $criteria; + $this->criterion = $criterion; $this->sortClauses = $sortClauses; $this->offset = $offset; $this->limit = $limit; } - public function addCriterion(CriterionInterface $criterion): void + public function getCriterion(): ?CriterionInterface { - $this->criteria[] = $criterion; + return $this->criterion; } - /** - * @return \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] - */ - public function getCriteria(): array + public function setCriterion(?CriterionInterface $criterion): void { - return $this->criteria; + $this->criterion = $criterion; } public function addSortClause(SortClause $sortClause): void diff --git a/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php b/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php index 35d529a6bb..ab150918d4 100644 --- a/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php +++ b/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php @@ -9,6 +9,7 @@ namespace Ibexa\Contracts\Core\Repository\Values\ContentType\Query; use Doctrine\DBAL\Query\QueryBuilder; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** * @template T of \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface @@ -22,6 +23,12 @@ public function supports(CriterionInterface $criterion): bool; /** * @param T $criterion + * + * @return string|\Doctrine\DBAL\Query\Expression\CompositeExpression */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void; + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ); } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php index 001d176538..69eac7f498 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds as ContainsFieldDefinitionIdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; final class ContainsFieldDefinitionIds extends Base { @@ -24,13 +25,16 @@ public function supports(CriterionInterface $criterion): bool /** * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds $criterion */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $qb->andWhere( - $qb->expr()->in( - 'a.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ), + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + $this->joinFieldDefinitions($qb); + + return $qb->expr()->in( + 'a.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php index 50d88d4b3f..fd90baf302 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds as ContentTypeGroupIdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; final class ContentTypeGroupIds extends Base { @@ -24,13 +25,16 @@ public function supports(CriterionInterface $criterion): bool /** * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds $criterion */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $qb->andWhere( - $qb->expr()->in( - 'ezcontentclass_classgroup_group_id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ), + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + $this->joinContentTypeGroupAssignmentTable($qb); + + return $qb->expr()->in( + 'g.group_id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php index 8efbb9a4bc..30799fdb3b 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers as IdentifiersCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; final class Identifiers extends Base { @@ -24,13 +25,14 @@ public function supports(CriterionInterface $criterion): bool /** * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers $criterion */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $qb->andWhere( - $qb->expr()->in( - 'c.identifier', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) - ), + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + return $qb->expr()->in( + 'c.identifier', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php index 094d8edfbf..8b49b6dbdf 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids as IdsCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; final class Ids extends Base { @@ -24,13 +25,14 @@ public function supports(CriterionInterface $criterion): bool /** * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids $criterion */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $qb->andWhere( - $qb->expr()->in( - 'c.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ), + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + return $qb->expr()->in( + 'c.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php index 64f91ff7df..8963675c52 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem as IsSystemCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; final class IsSystem extends Base { @@ -24,15 +25,17 @@ public function supports(CriterionInterface $criterion): bool /** * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem $criterion */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + $this->joinContentTypeGroupAssignmentTable($qb); $this->joinContentTypeGroup($qb); - $qb->andWhere( - $qb->expr()->eq( - 'ctg.is_system', - $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) - ) + return $qb->expr()->eq( + 'ctg.is_system', + $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php index 15ccc86dc1..6cc8999fe6 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php @@ -8,6 +8,7 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; +use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd as LogicalAndCriterion; @@ -16,14 +17,6 @@ final class LogicalAnd extends Base { - private CriterionVisitor $criterionVisitor; - - public function __construct( - CriterionVisitor $criterionVisitor - ) { - $this->criterionVisitor = $criterionVisitor; - } - public function supports(CriterionInterface $criterion): bool { return $criterion instanceof LogicalAndCriterion; @@ -34,21 +27,16 @@ public function supports(CriterionInterface $criterion): bool * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $constraints = []; - /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion */ - foreach ($criterion->getCriteria() as $criterion) { - $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); - if (null !== $constraint) { - $constraints[] = $constraint; - } - } - - if (empty($constraints)) { - return; + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): CompositeExpression { + $subexpressions = []; + foreach ($criterion->getCriteria() as $subCriterion) { + $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); } - $qb->andWhere($qb->expr()->and(...$constraints)); + return $qb->expr()->andX(...$subexpressions); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php index 408be9fb19..e7366d235e 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php @@ -16,14 +16,6 @@ final class LogicalNot extends Base { - private CriterionVisitor $criterionVisitor; - - public function __construct( - CriterionVisitor $criterionVisitor - ) { - $this->criterionVisitor = $criterionVisitor; - } - public function supports(CriterionInterface $criterion): bool { return $criterion instanceof LogicalNotCriterion; @@ -34,21 +26,18 @@ public function supports(CriterionInterface $criterion): bool * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $constraints = []; - /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot $criterion */ - foreach ($criterion->getCriteria() as $criterion) { - $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); - if (null !== $constraint) { - $constraints[] = $constraint; - } - } - - if (empty($constraints)) { - return; + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): string { + if (empty($criterion->getCriteria())) { + return ''; } - $qb->andWhere($qb->expr()->orX(...$constraints)); + return sprintf( + 'NOT (%s)', + $criterionVisitor->visitCriteria($qb, $criterion->getCriteria()[0]), + ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php index 272a357185..db63cd51d7 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php @@ -8,6 +8,7 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; +use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr as LogicalOrCriterion; @@ -16,14 +17,6 @@ final class LogicalOr extends Base { - private CriterionVisitor $criterionVisitor; - - public function __construct( - CriterionVisitor $criterionVisitor - ) { - $this->criterionVisitor = $criterionVisitor; - } - public function supports(CriterionInterface $criterion): bool { return $criterion instanceof LogicalOrCriterion; @@ -34,21 +27,16 @@ public function supports(CriterionInterface $criterion): bool * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException */ - public function apply(QueryBuilder $qb, CriterionInterface $criterion): void - { - $constraints = []; - /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr $criterion */ - foreach ($criterion->getCriteria() as $criterion) { - $constraint = $this->criterionVisitor->visitCriteria($qb, $criterion); - if (null !== $constraint) { - $constraints[] = $constraint; - } - } - - if (empty($constraints)) { - return; + public function apply( + CriterionVisitor $criterionVisitor, + QueryBuilder $qb, + CriterionInterface $criterion + ): CompositeExpression { + $subexpressions = []; + foreach ($criterion->getCriteria() as $subCriterion) { + $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); } - $qb->andWhere($qb->expr()->or(...$constraints)); + return $qb->expr()->orX(...$subexpressions); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php deleted file mode 100644 index 0238b2b99a..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContainsFieldDefinitionIds.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class ContainsFieldDefinitionIds implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof ContainsFieldDefinitionIdsCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - return $qb->expr()->in( - 'a.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php deleted file mode 100644 index 627887982c..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/ContentTypeGroupIds.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class ContentTypeGroupIds implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof ContentTypeGroupIdsCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - return $qb->expr()->in( - 'ezcontentclass_classgroup_group_id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php deleted file mode 100644 index 3d5f21075a..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/CriterionQueryBuilderInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - */ -final class Identifiers implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof IdentifiersCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - return $qb->expr()->in( - 'c.identifier', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php deleted file mode 100644 index a474283827..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/Ids.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class Ids implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof IdsCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - return $qb->expr()->in( - 'c.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php deleted file mode 100644 index 793183c159..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/IsSystem.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ -final class IsSystem implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof IsSystemCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - return $qb->expr()->eq( - 'ctg.is_system', - $qb->createNamedParameter($criterion->getValue(), ParameterType::BOOLEAN) - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php deleted file mode 100644 index d546647224..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalAnd.php +++ /dev/null @@ -1,42 +0,0 @@ - - */ -final class LogicalAnd implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof LogicalAndCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): CompositeExpression { - $subexpressions = []; - foreach ($criterion->getCriteria() as $subCriterion) { - $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); - } - - return $qb->expr()->and(...$subexpressions); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php deleted file mode 100644 index 2f73bb15cd..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalNot.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -final class LogicalNot implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof LogicalNotCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot $criterion - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): string { - if (empty($criterion->getCriteria())) { - return ''; - } - - return sprintf( - 'NOT (%s)', - $criterionVisitor->visitCriteria($qb, $criterion->getCriteria()[0]), - ); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php deleted file mode 100644 index a9f2501d8e..0000000000 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionQueryBuilder/LogicalOr.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ -final class LogicalOr implements CriterionQueryBuilderInterface -{ - public function supports(CriterionInterface $criterion): bool - { - return $criterion instanceof LogicalOrCriterion; - } - - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr $criterion - * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotImplementedException - */ - public function buildQueryConstraint( - CriterionVisitor $criterionVisitor, - QueryBuilder $qb, - CriterionInterface $criterion - ): CompositeExpression { - $subexpressions = []; - foreach ($criterion->getCriteria() as $subCriterion) { - $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); - } - - return $qb->expr()->or(...$subexpressions); - } -} diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php index 14767c77f6..b35194d5f2 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php @@ -16,16 +16,16 @@ final class CriterionVisitor { /** - * @var array<\Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> + * @var array> */ - private array $criterionQueryBuilders; + private array $criterionHandlers; /** - * @param iterable<\Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\CriterionQueryBuilderInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionQueryBuilders + * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionHandlers */ - public function __construct(iterable $criterionQueryBuilders) + public function __construct(iterable $criterionHandlers) { - $this->criterionQueryBuilders = iterator_to_array($criterionQueryBuilders); + $this->criterionHandlers = iterator_to_array($criterionHandlers); } /** @@ -37,9 +37,9 @@ public function visitCriteria( QueryBuilder $queryBuilder, CriterionInterface $criterion ) { - foreach ($this->criterionQueryBuilders as $criterionQueryBuilder) { - if ($criterionQueryBuilder->supports($criterion)) { - return $criterionQueryBuilder->buildQueryConstraint( + foreach ($this->criterionHandlers as $criterionHandler) { + if ($criterionHandler->supports($criterion)) { + return $criterionHandler->apply( $this, $queryBuilder, $criterion @@ -49,7 +49,7 @@ public function visitCriteria( throw new NotImplementedException( sprintf( - 'There is no Criterion Query Builder for %s Criterion', + 'There is no Criterion Handler for %s Criterion', get_class($criterion) ) ); diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index 0bae9fc3db..47abffe4e1 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -18,7 +18,6 @@ use Ibexa\Contracts\Core\Persistence\Content\Type\Group; use Ibexa\Contracts\Core\Persistence\Content\Type\Group\UpdateStruct as GroupUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Contracts\Core\Repository\Values\URL\Query\SortClause; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Base\Exceptions\NotFoundException; @@ -27,7 +26,6 @@ use Ibexa\Core\Persistence\Legacy\Content\StorageFieldDefinition; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway; use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway as SharedGateway; -use function Ibexa\PolyfillPhp82\iterator_to_array; use function sprintf; /** @@ -123,27 +121,22 @@ final class DoctrineDatabase extends Gateway */ private $languageMaskGenerator; - /** - * @var array> - */ - private array $criterionHandlers; + private Gateway\CriterionVisitor\CriterionVisitor $criterionVisitor; /** - * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionHandlers - * * @throws \Doctrine\DBAL\DBALException */ public function __construct( Connection $connection, SharedGateway $sharedGateway, MaskGenerator $languageMaskGenerator, - iterable $criterionHandlers + Gateway\CriterionVisitor\CriterionVisitor $criterionVisitor ) { $this->connection = $connection; $this->dbPlatform = $connection->getDatabasePlatform(); $this->sharedGateway = $sharedGateway; $this->languageMaskGenerator = $languageMaskGenerator; - $this->criterionHandlers = iterator_to_array($criterionHandlers); + $this->criterionVisitor = $criterionVisitor; } public function insertGroup(Group $group): int @@ -1428,11 +1421,11 @@ public function countContentTypes(?ContentTypeQuery $query = null): int { $queryBuilder = $this->connection->createQueryBuilder(); $queryBuilder - ->select('COUNT(ct.id)') - ->from(self::CONTENT_TYPE_TABLE, 'ct'); + ->select('COUNT(c.id)') + ->from(self::CONTENT_TYPE_TABLE, 'c'); - if ($query !== null && !empty($query->getCriteria())) { - $this->applyFilters($queryBuilder, $query->getCriteria()); + if ($query !== null && !empty($query->getCriterion())) { + $queryBuilder->andWhere($this->criterionVisitor->visitCriteria($queryBuilder, $query->getCriterion())); } return (int)$queryBuilder->execute()->fetchOne(); @@ -1447,8 +1440,8 @@ public function findContentTypes(?ContentTypeQuery $query = null): array return $queryBuilder->execute()->fetchAllAssociative(); } - if (!empty($query->getCriteria())) { - $this->applyFilters($queryBuilder, $query->getCriteria()); + if (!empty($query->getCriterion())) { + $queryBuilder->andWhere($this->criterionVisitor->visitCriteria($queryBuilder, $query->getCriterion())); } if ($query->getOffset() > 0) { @@ -1485,32 +1478,6 @@ private function getQuerySortingDirection(string $direction): string return self::SORT_DIRECTION_MAP[$direction]; } - /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface[] $criteria - */ - private function applyFilters(QueryBuilder $qb, array $criteria): void - { - foreach ($criteria as $criterion) { - $this->applyCriterion($qb, $criterion); - } - } - - private function applyCriterion(QueryBuilder $qb, CriterionInterface $criterion): void - { - foreach ($this->criterionHandlers as $handler) { - if ($handler->supports($criterion)) { - $handler->apply($qb, $criterion); - - return; - } - } - - throw new InvalidArgumentException( - get_class($criterion), - 'No handler found for criterion of type. Make sure the handler service is registered and tagged with "ibexa.content_type.criterion_handler".' - ); - } - /** * @throws \Doctrine\DBAL\DBALException */ diff --git a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml index c077d6f0e4..c7a55d080e 100644 --- a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml +++ b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml @@ -3,56 +3,20 @@ services: Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface: tags: [ 'ibexa.content_type.criterion_handler' ] - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\ContainsFieldDefinitionIds: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\ContentTypeGroupIds: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\Identifiers: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\Ids: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\IsSystem: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalAnd: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalOr: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionQueryBuilder\LogicalNot: - tags: [ 'ibexa.content_type.criterion_query_builder' ] - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor: arguments: - $criterionQueryBuilders: !tagged_iterator 'ibexa.content_type.criterion_query_builder' + $criterionHandlers: !tagged_iterator 'ibexa.content_type.criterion_handler' Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\: resource: '../../../../Persistence/Legacy/Content/Type/Gateway/CriterionHandler/*' - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalAnd: - arguments: - $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalOr: - arguments: - $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' - - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler\LogicalNot: - arguments: - $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' - Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\DoctrineDatabase.inner: class: Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\DoctrineDatabase arguments: $connection: '@ibexa.api.storage_engine.legacy.connection' $sharedGateway: '@Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway' $languageMaskGenerator: '@Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator' - $criterionHandlers: !tagged_iterator 'ibexa.content_type.criterion_handler' + $criterionVisitor: '@Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor' Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\ExceptionConversion: class: Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\ExceptionConversion diff --git a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php index 06b28f886c..c7c83f95b4 100644 --- a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php @@ -29,7 +29,7 @@ public function testCountContentTypesWithNullQuery(): void $contentTypesCount = $contentTypeService->countContentTypes(); - $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery([], [], 0, 999)); + $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery(null, [], 0, 999)); self::assertSame(count($contentTypesObjects), $contentTypesCount); } @@ -52,79 +52,79 @@ public function testCountContentTypes(ContentTypeQuery $query, int $expectedCoun public function dataProviderForTestCount(): iterable { yield 'identifiers' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new Identifiers(['folder', 'article']), - ]), + ), 2, ]; yield 'user group content type' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new ContentTypeGroupIds([2]), - ]), + ), 2, ]; yield 'ids' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new Ids([1]), - ]), + ), 1, ]; yield 'system group' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new IsSystem(false), - ]), + ), 3, ]; yield 'logical and' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new Identifiers(['folder', 'article']), new ContentTypeGroupIds([1]), ]), - ]), + ), 2, ]; yield 'logical or' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalOr([ new Identifiers(['folder', 'article']), new ContentTypeGroupIds([2]), ]), - ]), + ), 4, ]; yield 'logical not resulting in empty set' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ new Identifiers(['user', 'user_group']), ]), new ContentTypeGroupIds([2]), ]), - ]), + ), 0, ]; yield 'logical not' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ new Identifiers(['user']), ]), new ContentTypeGroupIds([2]), ]), - ]), + ), 1, ]; yield 'logical or outside with logical and inside' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalOr([ new LogicalAnd([ new Identifiers(['folder', 'article']), @@ -132,7 +132,7 @@ public function dataProviderForTestCount(): iterable ]), new Identifiers(['user']), ]), - ]), + ), 3, ]; } diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index 9a99d2c4c0..cab0ba5e22 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -58,9 +58,9 @@ public function testFindContentTypesAscSortedByIdentifier(): void $contentTypeService = self::getContentTypeService(); $contentTypes = $contentTypeService->findContentTypes( - new ContentTypeQuery([ + new ContentTypeQuery( new Identifiers(['folder', 'article', 'user', 'file']), - ]) + ) ); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), @@ -86,9 +86,9 @@ public function testFindContentTypesContainingFieldDefinitions(): void assert($fieldDefinitionToInclude !== null); $contentTypes = $contentTypeService->findContentTypes( - new ContentTypeQuery([ + new ContentTypeQuery( new ContainsFieldDefinitionIds([$fieldDefinitionToInclude->getId()]), - ]) + ) ); self::assertCount(1, $contentTypes); @@ -101,79 +101,79 @@ public function testFindContentTypesContainingFieldDefinitions(): void public function dataProviderForTestFindContentTypes(): iterable { yield 'identifiers' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new Identifiers(['folder', 'article']), - ]), + ), ['article', 'folder'], ]; yield 'user group content type' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new ContentTypeGroupIds([2]), - ]), + ), ['user', 'user_group'], ]; yield 'ids' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new Ids([1]), - ]), + ), ['folder'], ]; yield 'system group' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new IsSystem(false), - ]), + ), ['folder', 'user', 'user_group'], ]; yield 'logical and' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new Identifiers(['folder', 'article']), new ContentTypeGroupIds([1]), ]), - ]), + ), ['folder', 'article'], ]; yield 'logical or' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalOr([ new Identifiers(['folder', 'article']), new ContentTypeGroupIds([2]), ]), - ]), + ), ['folder', 'article', 'user', 'user_group'], ]; yield 'logical not resulting in empty set' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ new Identifiers(['user', 'user_group']), ]), new ContentTypeGroupIds([2]), ]), - ]), + ), [], ]; yield 'logical not' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ new Identifiers(['user']), ]), new ContentTypeGroupIds([2]), ]), - ]), + ), ['user_group'], ]; yield 'logical or outside with logical and inside' => [ - new ContentTypeQuery([ + new ContentTypeQuery( new LogicalOr([ new LogicalAnd([ new Identifiers(['folder', 'article']), @@ -181,7 +181,7 @@ public function dataProviderForTestFindContentTypes(): iterable ]), new Identifiers(['user']), ]), - ]), + ), ['folder', 'article', 'user'], ]; } diff --git a/tests/lib/Persistence/Legacy/Content/LanguageAwareTestCase.php b/tests/lib/Persistence/Legacy/Content/LanguageAwareTestCase.php index 02d95b6425..8ed932e875 100644 --- a/tests/lib/Persistence/Legacy/Content/LanguageAwareTestCase.php +++ b/tests/lib/Persistence/Legacy/Content/LanguageAwareTestCase.php @@ -8,6 +8,7 @@ use Ibexa\Core\Persistence; use Ibexa\Core\Persistence\Legacy\Content\Language\MaskGenerator as LanguageMaskGenerator; +use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; use Ibexa\Core\Search\Common\FieldNameGenerator; use Ibexa\Core\Search\Common\FieldRegistry; use Ibexa\Core\Search\Legacy\Content\Mapper\FullTextMapper; @@ -34,6 +35,8 @@ abstract class LanguageAwareTestCase extends TestCase */ protected $languageMaskGenerator; + protected CriterionVisitor $criterionVisitor; + /** * Returns a language handler mock. * @@ -64,6 +67,18 @@ protected function getLanguageMaskGenerator() return $this->languageMaskGenerator; } + /** + * Returns the criterion visitor. + */ + protected function getCriterionVisitor(): CriterionVisitor + { + if (!isset($this->criterionVisitor)) { + $this->criterionVisitor = new CriterionVisitor([]); + } + + return $this->criterionVisitor; + } + /** * Return definition-based transformation processor instance. * diff --git a/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php b/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php index ea36273ff4..8a9a807711 100644 --- a/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php +++ b/tests/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabaseTest.php @@ -1154,7 +1154,7 @@ protected function getGateway(): DoctrineDatabase $this->getDatabaseConnection(), $this->getSharedGateway(), $this->getLanguageMaskGenerator(), - [], + $this->getCriterionVisitor() ); } diff --git a/tests/lib/Search/Legacy/Content/AbstractTestCase.php b/tests/lib/Search/Legacy/Content/AbstractTestCase.php index dbdd2f17da..6ce356fa7b 100644 --- a/tests/lib/Search/Legacy/Content/AbstractTestCase.php +++ b/tests/lib/Search/Legacy/Content/AbstractTestCase.php @@ -89,7 +89,7 @@ protected function getContentTypeHandler(): SPIContentTypeHandler $this->getDatabaseConnection(), $this->getSharedGateway(), $this->getLanguageMaskGenerator(), - [] + $this->getCriterionVisitor() ), new ContentTypeMapper( $this->getConverterRegistry(), From 252733d450c9a80e3ad02502d740e0cc9e134e6d Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 10:31:04 +0200 Subject: [PATCH 05/14] IBX-10458: Refactor --- .../Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php | 2 +- .../Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php index 6cc8999fe6..f922f084d0 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php @@ -37,6 +37,6 @@ public function apply( $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); } - return $qb->expr()->andX(...$subexpressions); + return $qb->expr()->and(...$subexpressions); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php index db63cd51d7..6bcfa0f8fe 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php @@ -37,6 +37,6 @@ public function apply( $subexpressions[] = $criterionVisitor->visitCriteria($qb, $subCriterion); } - return $qb->expr()->orX(...$subexpressions); + return $qb->expr()->or(...$subexpressions); } } From 2a75be1d5004cebf0980b67ac7dd4359dc996a94 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 11:01:21 +0200 Subject: [PATCH 06/14] IBX-10458: Refactor --- .../Persistence/Content/Type/Handler.php | 2 +- .../Repository/ContentTypeService.php | 5 ++- .../Decorator/ContentTypeServiceDecorator.php | 3 +- .../Values/ContentType/SearchResult.php | 35 +++++++++++++++++++ .../Persistence/Cache/ContentTypeHandler.php | 8 +++++ .../Legacy/Content/Type/Handler.php | 13 ++++--- src/lib/Repository/ContentTypeService.php | 16 +++++---- .../SiteAccessAware/ContentTypeService.php | 3 +- .../CountContentTypesTest.php | 2 +- .../FindContentTypesTest.php | 6 ++-- 10 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 src/contracts/Repository/Values/ContentType/SearchResult.php diff --git a/src/contracts/Persistence/Content/Type/Handler.php b/src/contracts/Persistence/Content/Type/Handler.php index badabfb915..47efe1f5fe 100644 --- a/src/contracts/Persistence/Content/Type/Handler.php +++ b/src/contracts/Persistence/Content/Type/Handler.php @@ -95,7 +95,7 @@ public function loadContentTypeList(array $contentTypeIds): array; public function countContentTypes(?ContentTypeQuery $query = null): int; /** - * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] + * @return array{count: int, items: array} */ public function findContentTypes(?ContentTypeQuery $query = null): array; diff --git a/src/contracts/Repository/ContentTypeService.php b/src/contracts/Repository/ContentTypeService.php index c85e87951c..1ba25fdbeb 100644 --- a/src/contracts/Repository/ContentTypeService.php +++ b/src/contracts/Repository/ContentTypeService.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult; use Ibexa\Contracts\Core\Repository\Values\User\User; interface ContentTypeService @@ -176,10 +177,8 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan /** * @param list $prioritizedLanguages Used as prioritized language code on translated properties of returned object. - * - * @return array<\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType> */ - public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array; + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): SearchResult; public function countContentTypes(?ContentTypeQuery $query = null): int; diff --git a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php index 24bd79c01f..58aaab7fe1 100644 --- a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php +++ b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php @@ -20,6 +20,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult; use Ibexa\Contracts\Core\Repository\Values\User\User; abstract class ContentTypeServiceDecorator implements ContentTypeService @@ -108,7 +109,7 @@ public function loadContentTypeList( return $this->innerService->loadContentTypeList($contentTypeIds, $prioritizedLanguages); } - public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): SearchResult { return $this->innerService->findContentTypes($query, $prioritizedLanguages); } diff --git a/src/contracts/Repository/Values/ContentType/SearchResult.php b/src/contracts/Repository/Values/ContentType/SearchResult.php new file mode 100644 index 0000000000..8c000a0cc6 --- /dev/null +++ b/src/contracts/Repository/Values/ContentType/SearchResult.php @@ -0,0 +1,35 @@ + + */ +final class SearchResult extends ValueObject implements IteratorAggregate +{ + public int $totalCount = 0; + + /** + * @var array + */ + public array $items = []; + + /** + * @return \Traversable + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } +} diff --git a/src/lib/Persistence/Cache/ContentTypeHandler.php b/src/lib/Persistence/Cache/ContentTypeHandler.php index dc56d30464..ad4e8d9991 100644 --- a/src/lib/Persistence/Cache/ContentTypeHandler.php +++ b/src/lib/Persistence/Cache/ContentTypeHandler.php @@ -235,11 +235,19 @@ function () use ($groupId) { public function countContentTypes(?ContentTypeQuery $query = null): int { + $this->logger->logCall(__METHOD__, [ + 'query' => $query, + ]); + return $this->persistenceHandler->contentTypeHandler()->countContentTypes($query); } public function findContentTypes(?ContentTypeQuery $query = null): array { + $this->logger->logCall(__METHOD__, [ + 'query' => $query, + ]); + return $this->persistenceHandler->contentTypeHandler()->findContentTypes($query); } diff --git a/src/lib/Persistence/Legacy/Content/Type/Handler.php b/src/lib/Persistence/Legacy/Content/Type/Handler.php index b0d9a4b221..5b7c601419 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Type/Handler.php @@ -201,15 +201,18 @@ public function countContentTypes(?ContentTypeQuery $query = null): int return $this->contentTypeGateway->countContentTypes($query); } - /** - * @return \Ibexa\Contracts\Core\Persistence\Content\Type[] - */ public function findContentTypes(?ContentTypeQuery $query = null): array { - return $this->mapper->extractTypesFromRows( - $this->contentTypeGateway->findContentTypes($query), + $rows = $this->contentTypeGateway->findContentTypes($query); + $items = $this->mapper->extractTypesFromRows( + $rows, true ); + + return [ + 'count' => count($items), + 'items' => $items, + ]; } /** diff --git a/src/lib/Repository/ContentTypeService.php b/src/lib/Repository/ContentTypeService.php index 08d10d9192..19bdd9353e 100644 --- a/src/lib/Repository/ContentTypeService.php +++ b/src/lib/Repository/ContentTypeService.php @@ -35,6 +35,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult; use Ibexa\Contracts\Core\Repository\Values\User\User; use Ibexa\Core\Base\Exceptions\BadStateException; use Ibexa\Core\Base\Exceptions\ContentTypeFieldDefinitionValidationException; @@ -933,19 +934,22 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan return $contentTypes; } - public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): SearchResult { - $contentTypes = []; - $persistenceContentTypes = $this->contentTypeHandler->findContentTypes($query); + $results = $this->contentTypeHandler->findContentTypes($query); - foreach ($persistenceContentTypes as $persistenceContentType) { - $contentTypes[] = $this->contentTypeDomainMapper->buildContentTypeDomainObject( + $items = []; + foreach ($results['items'] as $persistenceContentType) { + $items[] = $this->contentTypeDomainMapper->buildContentTypeDomainObject( $persistenceContentType, $prioritizedLanguages ); } - return $contentTypes; + return new SearchResult([ + 'totalCount' => $results['count'], + 'items' => $items, + ]); } public function countContentTypes(?ContentTypeQuery $query = null): int diff --git a/src/lib/Repository/SiteAccessAware/ContentTypeService.php b/src/lib/Repository/SiteAccessAware/ContentTypeService.php index 054f3d22cf..bdec60da6b 100644 --- a/src/lib/Repository/SiteAccessAware/ContentTypeService.php +++ b/src/lib/Repository/SiteAccessAware/ContentTypeService.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult; use Ibexa\Contracts\Core\Repository\Values\User\User; /** @@ -120,7 +121,7 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan return $this->service->loadContentTypeList($contentTypeIds, $prioritizedLanguages); } - public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): array + public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): SearchResult { return $this->service->findContentTypes($query, $prioritizedLanguages); } diff --git a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php index c7c83f95b4..af99f9fced 100644 --- a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php @@ -31,7 +31,7 @@ public function testCountContentTypesWithNullQuery(): void $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery(null, [], 0, 999)); - self::assertSame(count($contentTypesObjects), $contentTypesCount); + self::assertSame($contentTypesObjects->totalCount, $contentTypesCount); } /** diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index cab0ba5e22..0c6106d7aa 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -46,7 +46,7 @@ public function testFindContentTypes(ContentTypeQuery $query, array $expectedIde $contentTypes = $contentTypeService->findContentTypes($query); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), - $contentTypes + $contentTypes->items ); self::assertCount(count($expectedIdentifiers), $identifiers); @@ -64,7 +64,7 @@ public function testFindContentTypesAscSortedByIdentifier(): void ); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), - $contentTypes + $contentTypes->items ); self::assertCount(4, $identifiers); @@ -92,7 +92,7 @@ public function testFindContentTypesContainingFieldDefinitions(): void ); self::assertCount(1, $contentTypes); - self::assertSame('folder', $contentTypes[0]->getIdentifier()); + self::assertSame('folder', $contentTypes->items[0]->getIdentifier()); } /** From 0b2ec1f93f76cd897cf1f80db162994f8bc993de Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 11:06:32 +0200 Subject: [PATCH 07/14] IBX-10458: Fixup --- .../lib/Repository/SiteAccessAware/ContentTypeServiceTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php index 7f433a42c4..19a1f8556c 100644 --- a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php +++ b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCreateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; +use Ibexa\Contracts\Core\Repository\Values\ContentType\SearchResult; use Ibexa\Core\Repository\SiteAccessAware\ContentTypeService; use Ibexa\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\Repository\Values\ContentType\ContentTypeCreateStruct; @@ -101,7 +102,7 @@ public function providerForPassTroughMethods() ['deleteUserDrafts', [14], null], - ['findContentTypes', [new ContentTypeQuery()], [$contentType]], + ['findContentTypes', [new ContentTypeQuery()], new SearchResult()], ['countContentTypes', [new ContentTypeQuery()], 1], ]; From b56a9dafab0d2dbcb8476d501bd19e7e1f4bb615 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 14:57:59 +0200 Subject: [PATCH 08/14] IBX-10458: Fixup --- src/contracts/Repository/Values/ContentType/Query/Base.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/contracts/Repository/Values/ContentType/Query/Base.php b/src/contracts/Repository/Values/ContentType/Query/Base.php index 729fd66992..ec43aee6c3 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Base.php +++ b/src/contracts/Repository/Values/ContentType/Query/Base.php @@ -28,7 +28,6 @@ protected function joinContentTypeGroup(QueryBuilder $query): void 'ctg', 'g.contentclass_id = ctg.id' ); - $query->addSelect('ctg.id'); } } @@ -49,7 +48,6 @@ protected function joinFieldDefinitions(QueryBuilder $query): void 'c.version = a.version' ) ); - $query->addSelect('a.id'); } } @@ -70,7 +68,6 @@ protected function joinContentTypeGroupAssignmentTable(QueryBuilder $query): voi 'c.version = g.contentclass_version', ) ); - $query->addSelect('g.group_id'); } } From 6eff1d17dfd24f36d06979bad0ea18ab9000f7f8 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 13 Aug 2025 15:31:59 +0200 Subject: [PATCH 09/14] IBX-10458: Fixup --- .../Repository/ContentTypeService/FindContentTypesTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index 0c6106d7aa..defc4dc004 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -18,6 +18,7 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause\Identifier; use Ibexa\Tests\Integration\Core\RepositoryTestCase; /** @@ -60,7 +61,8 @@ public function testFindContentTypesAscSortedByIdentifier(): void $contentTypes = $contentTypeService->findContentTypes( new ContentTypeQuery( new Identifiers(['folder', 'article', 'user', 'file']), - ) + [new Identifier()] + ), ); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), From 34869c6d1ca6b3d1461f004a178b630496971ddb Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Thu, 14 Aug 2025 22:28:28 +0200 Subject: [PATCH 10/14] IBX-10458: Applied review remarks --- .../Type}/CriterionHandlerInterface.php | 7 +- .../ContentType/Query/ContentTypeQuery.php | 4 +- ...pIds.php => ContainsFieldDefinitionId.php} | 18 ++--- ...Identifiers.php => ContentTypeGroupId.php} | 18 ++--- .../Criterion/{Ids.php => ContentTypeId.php} | 18 ++--- ...itionIds.php => ContentTypeIdentifier.php} | 18 ++--- .../ContentType/Query/Criterion/IsSystem.php | 2 +- .../Values/ContentType/SearchResult.php | 17 ++++- ...nIds.php => ContainsFieldDefinitionId.php} | 23 +++++-- ...ypeGroupIds.php => ContentTypeGroupId.php} | 23 +++++-- .../{Ids.php => ContentTypeId.php} | 23 +++++-- ...ntifiers.php => ContentTypeIdentifier.php} | 23 +++++-- .../Gateway/CriterionHandler/IsSystem.php | 2 +- .../Gateway/CriterionHandler/LogicalAnd.php | 2 +- .../Gateway/CriterionHandler/LogicalNot.php | 2 +- .../Gateway/CriterionHandler/LogicalOr.php | 2 +- .../CriterionVisitor/CriterionVisitor.php | 4 +- .../Content/Type/Gateway/DoctrineDatabase.php | 7 +- .../Values/ContentType/Query/Base.php | 5 +- .../storage_engines/legacy/content_type.yml | 2 +- .../CountContentTypesTest.php | 36 +++++----- .../FindContentTypesTest.php | 69 ++++++++++++------- 22 files changed, 198 insertions(+), 127 deletions(-) rename src/contracts/{Repository/Values/ContentType/Query => Persistence/Content/Type}/CriterionHandlerInterface.php (70%) rename src/contracts/Repository/Values/ContentType/Query/Criterion/{ContentTypeGroupIds.php => ContainsFieldDefinitionId.php} (62%) rename src/contracts/Repository/Values/ContentType/Query/Criterion/{Identifiers.php => ContentTypeGroupId.php} (61%) rename src/contracts/Repository/Values/ContentType/Query/Criterion/{Ids.php => ContentTypeId.php} (63%) rename src/contracts/Repository/Values/ContentType/Query/Criterion/{ContainsFieldDefinitionIds.php => ContentTypeIdentifier.php} (61%) rename src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/{ContainsFieldDefinitionIds.php => ContainsFieldDefinitionId.php} (58%) rename src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/{ContentTypeGroupIds.php => ContentTypeGroupId.php} (60%) rename src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/{Ids.php => ContentTypeId.php} (59%) rename src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/{Identifiers.php => ContentTypeIdentifier.php} (57%) rename src/{contracts => lib}/Repository/Values/ContentType/Query/Base.php (89%) diff --git a/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php b/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php similarity index 70% rename from src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php rename to src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php index ab150918d4..348b922905 100644 --- a/src/contracts/Repository/Values/ContentType/Query/CriterionHandlerInterface.php +++ b/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php @@ -6,9 +6,10 @@ */ declare(strict_types=1); -namespace Ibexa\Contracts\Core\Repository\Values\ContentType\Query; +namespace Ibexa\Contracts\Core\Persistence\Content\Type; use Doctrine\DBAL\Query\QueryBuilder; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** @@ -17,12 +18,12 @@ interface CriterionHandlerInterface { /** - * @param T $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface $criterion */ public function supports(CriterionInterface $criterion): bool; /** - * @param T $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface $criterion * * @return string|\Doctrine\DBAL\Query\Expression\CompositeExpression */ diff --git a/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php index 5292bdc9d5..e2618e1718 100644 --- a/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php +++ b/src/contracts/Repository/Values/ContentType/Query/ContentTypeQuery.php @@ -10,6 +10,8 @@ final class ContentTypeQuery { + public const DEFAULT_LIMIT = 25; + private ?CriterionInterface $criterion; /** @var \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\SortClause[] */ @@ -26,7 +28,7 @@ public function __construct( ?CriterionInterface $criterion = null, array $sortClauses = [], int $offset = 0, - int $limit = 25 + int $limit = self::DEFAULT_LIMIT ) { $this->criterion = $criterion; $this->sortClauses = $sortClauses; diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionId.php similarity index 62% rename from src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php rename to src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionId.php index e3d4a622dc..9cf6dbba98 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupIds.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionId.php @@ -10,31 +10,31 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -final class ContentTypeGroupIds implements CriterionInterface +final class ContainsFieldDefinitionId implements CriterionInterface { - /** @var list */ - private array $value; + /** @var list|int */ + private $value; /** - * @param list $value + * @param list|int $value */ - public function __construct(array $value) + public function __construct($value) { $this->value = $value; } /** - * @return list + * @return list|int */ - public function getValue(): array + public function getValue() { return $this->value; } /** - * @param list $value + * @param list|int $value */ - public function setValue(array $value): void + public function setValue($value): void { $this->value = $value; } diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupId.php similarity index 61% rename from src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php rename to src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupId.php index 46b85fbf94..97b205c314 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/Identifiers.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeGroupId.php @@ -10,31 +10,31 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -final class Identifiers implements CriterionInterface +final class ContentTypeGroupId implements CriterionInterface { - /** @var list */ - private array $value; + /** @var list|int */ + private $value; /** - * @param list $value + * @param list|int $value */ - public function __construct(array $value) + public function __construct($value) { $this->value = $value; } /** - * @return list + * @return list|int */ - public function getValue(): array + public function getValue() { return $this->value; } /** - * @param list $value + * @param list|int $value */ - public function setValue(array $value): void + public function setValue($value): void { $this->value = $value; } diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeId.php similarity index 63% rename from src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php rename to src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeId.php index d8a6fb20ce..d3d115ad1a 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/Ids.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeId.php @@ -10,31 +10,31 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -final class Ids implements CriterionInterface +final class ContentTypeId implements CriterionInterface { - /** @var list */ - private array $value; + /** @var list|int */ + private $value; /** - * @param list $value + * @param list|int $value */ - public function __construct(array $value) + public function __construct($value) { $this->value = $value; } /** - * @return list + * @return list|int */ - public function getValue(): array + public function getValue() { return $this->value; } /** - * @param list $value + * @param list|int $value */ - public function setValue(array $value): void + public function setValue($value): void { $this->value = $value; } diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeIdentifier.php similarity index 61% rename from src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php rename to src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeIdentifier.php index 234490fe3b..a61ab9a20d 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/ContainsFieldDefinitionIds.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/ContentTypeIdentifier.php @@ -10,31 +10,31 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; -final class ContainsFieldDefinitionIds implements CriterionInterface +final class ContentTypeIdentifier implements CriterionInterface { - /** @var list */ - private array $value; + /** @var list|string */ + private $value; /** - * @param list $value + * @param list|string $value */ - public function __construct(array $value) + public function __construct($value) { $this->value = $value; } /** - * @return list + * @return list|string */ - public function getValue(): array + public function getValue() { return $this->value; } /** - * @param list $value + * @param list|string $value */ - public function setValue(array $value): void + public function setValue($value): void { $this->value = $value; } diff --git a/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php b/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php index 6e4af0d9c5..d01d77d280 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php +++ b/src/contracts/Repository/Values/ContentType/Query/Criterion/IsSystem.php @@ -14,7 +14,7 @@ final class IsSystem implements CriterionInterface { private bool $value; - public function __construct(bool $value) + public function __construct(bool $value = true) { $this->value = $value; } diff --git a/src/contracts/Repository/Values/ContentType/SearchResult.php b/src/contracts/Repository/Values/ContentType/SearchResult.php index 8c000a0cc6..6288f3512e 100644 --- a/src/contracts/Repository/Values/ContentType/SearchResult.php +++ b/src/contracts/Repository/Values/ContentType/SearchResult.php @@ -18,12 +18,23 @@ */ final class SearchResult extends ValueObject implements IteratorAggregate { - public int $totalCount = 0; + protected int $totalCount = 0; + + /** @var array */ + protected array $items = []; + + public function getTotalCount(): int + { + return $this->totalCount; + } /** - * @var array + * @return array */ - public array $items = []; + public function getContentTypes(): array + { + return $this->items; + } /** * @return \Traversable diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionId.php similarity index 58% rename from src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php rename to src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionId.php index 69eac7f498..73f3ea3256 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContainsFieldDefinitionId.php @@ -9,21 +9,22 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds as ContainsFieldDefinitionIdsCriterion; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionId as ContainsFieldDefinitionIdCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; -final class ContainsFieldDefinitionIds extends Base +final class ContainsFieldDefinitionId extends Base { public function supports(CriterionInterface $criterion): bool { - return $criterion instanceof ContainsFieldDefinitionIdsCriterion; + return $criterion instanceof ContainsFieldDefinitionIdCriterion; } /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionId $criterion */ public function apply( CriterionVisitor $criterionVisitor, @@ -32,9 +33,17 @@ public function apply( ): string { $this->joinFieldDefinitions($qb); - return $qb->expr()->in( + $value = $criterion->getValue(); + if (is_array($value)) { + return $qb->expr()->in( + 'a.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ); + } + + return $qb->expr()->eq( 'a.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + $qb->createNamedParameter($criterion->getValue(), ParameterType::INTEGER) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupId.php similarity index 60% rename from src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php rename to src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupId.php index fd90baf302..c05862620d 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupIds.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeGroupId.php @@ -9,21 +9,22 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds as ContentTypeGroupIdsCriterion; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupId as ContentTypeGroupIdCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; -final class ContentTypeGroupIds extends Base +final class ContentTypeGroupId extends Base { public function supports(CriterionInterface $criterion): bool { - return $criterion instanceof ContentTypeGroupIdsCriterion; + return $criterion instanceof ContentTypeGroupIdCriterion; } /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupId $criterion */ public function apply( CriterionVisitor $criterionVisitor, @@ -32,9 +33,17 @@ public function apply( ): string { $this->joinContentTypeGroupAssignmentTable($qb); - return $qb->expr()->in( + $value = $criterion->getValue(); + if (is_array($value)) { + return $qb->expr()->in( + 'g.group_id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ); + } + + return $qb->expr()->eq( 'g.group_id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + $qb->createNamedParameter($criterion->getValue(), ParameterType::INTEGER) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeId.php similarity index 59% rename from src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php rename to src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeId.php index 8b49b6dbdf..45ab537acd 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Ids.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeId.php @@ -9,30 +9,39 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids as IdsCriterion; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeId as ContentTypeIdCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; -final class Ids extends Base +final class ContentTypeId extends Base { public function supports(CriterionInterface $criterion): bool { - return $criterion instanceof IdsCriterion; + return $criterion instanceof ContentTypeIdCriterion; } /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeId $criterion */ public function apply( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion ): string { - return $qb->expr()->in( + $value = $criterion->getValue(); + if (is_array($value)) { + return $qb->expr()->in( + 'c.id', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + ); + } + + return $qb->expr()->eq( 'c.id', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_INT_ARRAY) + $qb->createNamedParameter($criterion->getValue(), ParameterType::INTEGER) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeIdentifier.php similarity index 57% rename from src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php rename to src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeIdentifier.php index 30799fdb3b..71fbb17b6d 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/Identifiers.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/ContentTypeIdentifier.php @@ -9,30 +9,39 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers as IdentifiersCriterion; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeIdentifier as ContentTypeIdentifierCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; -final class Identifiers extends Base +final class ContentTypeIdentifier extends Base { public function supports(CriterionInterface $criterion): bool { - return $criterion instanceof IdentifiersCriterion; + return $criterion instanceof ContentTypeIdentifierCriterion; } /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers $criterion + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeIdentifier $criterion */ public function apply( CriterionVisitor $criterionVisitor, QueryBuilder $qb, CriterionInterface $criterion ): string { - return $qb->expr()->in( + $value = $criterion->getValue(); + if (is_array($value)) { + return $qb->expr()->in( + 'c.identifier', + $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + ); + } + + return $qb->expr()->eq( 'c.identifier', - $qb->createNamedParameter($criterion->getValue(), Connection::PARAM_STR_ARRAY) + $qb->createNamedParameter($criterion->getValue(), ParameterType::STRING) ); } } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php index 8963675c52..6fe2f31408 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/IsSystem.php @@ -10,10 +10,10 @@ use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem as IsSystemCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; final class IsSystem extends Base { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php index f922f084d0..fbfd70bc4a 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalAnd.php @@ -10,10 +10,10 @@ use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd as LogicalAndCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; final class LogicalAnd extends Base { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php index e7366d235e..0ba8587270 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalNot.php @@ -9,10 +9,10 @@ namespace Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionHandler; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot as LogicalNotCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; final class LogicalNot extends Base { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php index 6bcfa0f8fe..314f5d2e2e 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionHandler/LogicalOr.php @@ -10,10 +10,10 @@ use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Base; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalOr as LogicalOrCriterion; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; +use Ibexa\Core\Repository\Values\ContentType\Query\Base; final class LogicalOr extends Base { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php index b35194d5f2..c1ccf84213 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/CriterionVisitor/CriterionVisitor.php @@ -16,12 +16,12 @@ final class CriterionVisitor { /** - * @var array> + * @var array> */ private array $criterionHandlers; /** - * @param iterable<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionHandlers + * @param iterable<\Ibexa\Contracts\Core\Persistence\Content\Type\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface>> $criterionHandlers */ public function __construct(iterable $criterionHandlers) { diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index 47abffe4e1..a7089e6bec 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -1434,9 +1434,10 @@ public function countContentTypes(?ContentTypeQuery $query = null): int public function findContentTypes(?ContentTypeQuery $query = null): array { $queryBuilder = $this->getLoadTypeQueryBuilder(); - $queryBuilder->setMaxResults(25); if ($query === null) { + $queryBuilder->setMaxResults(ContentTypeQuery::DEFAULT_LIMIT); + return $queryBuilder->execute()->fetchAllAssociative(); } @@ -1448,9 +1449,7 @@ public function findContentTypes(?ContentTypeQuery $query = null): array $queryBuilder->setFirstResult($query->getOffset()); } - if ($query->getLimit() > 0) { - $queryBuilder->setMaxResults($query->getLimit()); - } + $queryBuilder->setMaxResults($query->getLimit() > 0 ? $query->getLimit() : null); foreach ($query->getSortClauses() as $sortClause) { $column = sprintf('c.%s', $sortClause->target); diff --git a/src/contracts/Repository/Values/ContentType/Query/Base.php b/src/lib/Repository/Values/ContentType/Query/Base.php similarity index 89% rename from src/contracts/Repository/Values/ContentType/Query/Base.php rename to src/lib/Repository/Values/ContentType/Query/Base.php index ec43aee6c3..4d395aab86 100644 --- a/src/contracts/Repository/Values/ContentType/Query/Base.php +++ b/src/lib/Repository/Values/ContentType/Query/Base.php @@ -6,13 +6,14 @@ */ declare(strict_types=1); -namespace Ibexa\Contracts\Core\Repository\Values\ContentType\Query; +namespace Ibexa\Core\Repository\Values\ContentType\Query; use Doctrine\DBAL\Query\QueryBuilder; +use Ibexa\Contracts\Core\Persistence\Content\Type\CriterionHandlerInterface; use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway; /** - * @implements \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface> + * @implements \Ibexa\Contracts\Core\Persistence\Content\Type\CriterionHandlerInterface<\Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface> */ abstract class Base implements CriterionHandlerInterface { diff --git a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml index c7a55d080e..b98b1ed5bc 100644 --- a/src/lib/Resources/settings/storage_engines/legacy/content_type.yml +++ b/src/lib/Resources/settings/storage_engines/legacy/content_type.yml @@ -1,6 +1,6 @@ services: _instanceof: - Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionHandlerInterface: + Ibexa\Contracts\Core\Persistence\Content\Type\CriterionHandlerInterface: tags: [ 'ibexa.content_type.criterion_handler' ] Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor: diff --git a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php index af99f9fced..b9b42b6b28 100644 --- a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php @@ -9,9 +9,9 @@ namespace Ibexa\Tests\Integration\Core\Repository\ContentTypeService; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupId; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeId; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeIdentifier; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot; @@ -31,7 +31,7 @@ public function testCountContentTypesWithNullQuery(): void $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery(null, [], 0, 999)); - self::assertSame($contentTypesObjects->totalCount, $contentTypesCount); + self::assertSame($contentTypesObjects->getTotalCount(), $contentTypesCount); } /** @@ -53,21 +53,21 @@ public function dataProviderForTestCount(): iterable { yield 'identifiers' => [ new ContentTypeQuery( - new Identifiers(['folder', 'article']), + new ContentTypeIdentifier(['folder', 'article']), ), 2, ]; yield 'user group content type' => [ new ContentTypeQuery( - new ContentTypeGroupIds([2]), + new ContentTypeGroupId([2]), ), 2, ]; yield 'ids' => [ new ContentTypeQuery( - new Ids([1]), + new ContentTypeId([1]), ), 1, ]; @@ -82,8 +82,8 @@ public function dataProviderForTestCount(): iterable yield 'logical and' => [ new ContentTypeQuery( new LogicalAnd([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([1]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([1]), ]), ), 2, @@ -92,8 +92,8 @@ public function dataProviderForTestCount(): iterable yield 'logical or' => [ new ContentTypeQuery( new LogicalOr([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([2]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([2]), ]), ), 4, @@ -103,9 +103,9 @@ public function dataProviderForTestCount(): iterable new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ - new Identifiers(['user', 'user_group']), + new ContentTypeIdentifier(['user', 'user_group']), ]), - new ContentTypeGroupIds([2]), + new ContentTypeGroupId([2]), ]), ), 0, @@ -115,9 +115,9 @@ public function dataProviderForTestCount(): iterable new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ - new Identifiers(['user']), + new ContentTypeIdentifier(['user']), ]), - new ContentTypeGroupIds([2]), + new ContentTypeGroupId([2]), ]), ), 1, @@ -127,10 +127,10 @@ public function dataProviderForTestCount(): iterable new ContentTypeQuery( new LogicalOr([ new LogicalAnd([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([1]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([1]), ]), - new Identifiers(['user']), + new ContentTypeIdentifier(['user']), ]), ), 3, diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index defc4dc004..76f8679cee 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -10,10 +10,10 @@ use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\ContentTypeQuery; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionIds; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupIds; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Identifiers; -use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\Ids; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContainsFieldDefinitionId; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeGroupId; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeId; +use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\ContentTypeIdentifier; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\IsSystem; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalAnd; use Ibexa\Contracts\Core\Repository\Values\ContentType\Query\Criterion\LogicalNot; @@ -47,7 +47,7 @@ public function testFindContentTypes(ContentTypeQuery $query, array $expectedIde $contentTypes = $contentTypeService->findContentTypes($query); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), - $contentTypes->items + $contentTypes->getContentTypes(), ); self::assertCount(count($expectedIdentifiers), $identifiers); @@ -60,13 +60,13 @@ public function testFindContentTypesAscSortedByIdentifier(): void $contentTypes = $contentTypeService->findContentTypes( new ContentTypeQuery( - new Identifiers(['folder', 'article', 'user', 'file']), + new ContentTypeIdentifier(['folder', 'article', 'user', 'file']), [new Identifier()] ), ); $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), - $contentTypes->items + $contentTypes->getContentTypes() ); self::assertCount(4, $identifiers); @@ -89,12 +89,12 @@ public function testFindContentTypesContainingFieldDefinitions(): void $contentTypes = $contentTypeService->findContentTypes( new ContentTypeQuery( - new ContainsFieldDefinitionIds([$fieldDefinitionToInclude->getId()]), + new ContainsFieldDefinitionId([$fieldDefinitionToInclude->getId()]), ) ); self::assertCount(1, $contentTypes); - self::assertSame('folder', $contentTypes->items[0]->getIdentifier()); + self::assertSame('folder', $contentTypes->getContentTypes()[0]->getIdentifier()); } /** @@ -104,21 +104,42 @@ public function dataProviderForTestFindContentTypes(): iterable { yield 'identifiers' => [ new ContentTypeQuery( - new Identifiers(['folder', 'article']), + new ContentTypeIdentifier(['folder', 'article']), ), ['article', 'folder'], ]; - yield 'user group content type' => [ + yield 'single identifier' => [ new ContentTypeQuery( - new ContentTypeGroupIds([2]), + new ContentTypeIdentifier('folder'), + ), + ['folder'], + ]; + + yield 'user group' => [ + new ContentTypeQuery( + new ContentTypeGroupId([2]), + ), + ['user', 'user_group'], + ]; + + yield 'single user group' => [ + new ContentTypeQuery( + new ContentTypeGroupId(2), ), ['user', 'user_group'], ]; yield 'ids' => [ new ContentTypeQuery( - new Ids([1]), + new ContentTypeId([1]), + ), + ['folder'], + ]; + + yield 'single id' => [ + new ContentTypeQuery( + new ContentTypeId(1), ), ['folder'], ]; @@ -133,8 +154,8 @@ public function dataProviderForTestFindContentTypes(): iterable yield 'logical and' => [ new ContentTypeQuery( new LogicalAnd([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([1]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([1]), ]), ), ['folder', 'article'], @@ -143,8 +164,8 @@ public function dataProviderForTestFindContentTypes(): iterable yield 'logical or' => [ new ContentTypeQuery( new LogicalOr([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([2]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([2]), ]), ), ['folder', 'article', 'user', 'user_group'], @@ -154,9 +175,9 @@ public function dataProviderForTestFindContentTypes(): iterable new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ - new Identifiers(['user', 'user_group']), + new ContentTypeIdentifier(['user', 'user_group']), ]), - new ContentTypeGroupIds([2]), + new ContentTypeGroupId([2]), ]), ), [], @@ -166,9 +187,9 @@ public function dataProviderForTestFindContentTypes(): iterable new ContentTypeQuery( new LogicalAnd([ new LogicalNot([ - new Identifiers(['user']), + new ContentTypeIdentifier(['user']), ]), - new ContentTypeGroupIds([2]), + new ContentTypeGroupId([2]), ]), ), ['user_group'], @@ -178,10 +199,10 @@ public function dataProviderForTestFindContentTypes(): iterable new ContentTypeQuery( new LogicalOr([ new LogicalAnd([ - new Identifiers(['folder', 'article']), - new ContentTypeGroupIds([1]), + new ContentTypeIdentifier(['folder', 'article']), + new ContentTypeGroupId([1]), ]), - new Identifiers(['user']), + new ContentTypeIdentifier(['user']), ]), ), ['folder', 'article', 'user'], From 6204f3e3d57a1ca708cf47438ae63387103633ab Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Sun, 17 Aug 2025 19:38:50 +0200 Subject: [PATCH 11/14] IBX-10458: Remove `countContentTypes` --- .../Persistence/Content/Type/Handler.php | 2 - .../Repository/ContentTypeService.php | 2 - .../Decorator/ContentTypeServiceDecorator.php | 5 - .../Persistence/Cache/ContentTypeHandler.php | 9 -- .../Legacy/Content/Type/Gateway.php | 2 - .../Content/Type/Gateway/DoctrineDatabase.php | 14 -- .../Type/Gateway/ExceptionConversion.php | 9 -- .../Legacy/Content/Type/Handler.php | 5 - .../Content/Type/MemoryCachingHandler.php | 5 - src/lib/Repository/ContentTypeService.php | 5 - .../SiteAccessAware/ContentTypeService.php | 5 - .../CountContentTypesTest.php | 139 ------------------ 12 files changed, 202 deletions(-) delete mode 100644 tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php diff --git a/src/contracts/Persistence/Content/Type/Handler.php b/src/contracts/Persistence/Content/Type/Handler.php index 47efe1f5fe..c66c31f326 100644 --- a/src/contracts/Persistence/Content/Type/Handler.php +++ b/src/contracts/Persistence/Content/Type/Handler.php @@ -92,8 +92,6 @@ public function loadContentTypes($groupId, $status = Type::STATUS_DEFINED); */ public function loadContentTypeList(array $contentTypeIds): array; - public function countContentTypes(?ContentTypeQuery $query = null): int; - /** * @return array{count: int, items: array} */ diff --git a/src/contracts/Repository/ContentTypeService.php b/src/contracts/Repository/ContentTypeService.php index 1ba25fdbeb..e1fabf86d6 100644 --- a/src/contracts/Repository/ContentTypeService.php +++ b/src/contracts/Repository/ContentTypeService.php @@ -180,8 +180,6 @@ public function loadContentTypeList(array $contentTypeIds, array $prioritizedLan */ public function findContentTypes(?ContentTypeQuery $query = null, array $prioritizedLanguages = []): SearchResult; - public function countContentTypes(?ContentTypeQuery $query = null): int; - /** * Get content type objects which belong to the given content type group. * diff --git a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php index 58aaab7fe1..c363374e41 100644 --- a/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php +++ b/src/contracts/Repository/Decorator/ContentTypeServiceDecorator.php @@ -114,11 +114,6 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit return $this->innerService->findContentTypes($query, $prioritizedLanguages); } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - return $this->innerService->countContentTypes($query); - } - public function loadContentTypes( ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = [] diff --git a/src/lib/Persistence/Cache/ContentTypeHandler.php b/src/lib/Persistence/Cache/ContentTypeHandler.php index ad4e8d9991..0152d85647 100644 --- a/src/lib/Persistence/Cache/ContentTypeHandler.php +++ b/src/lib/Persistence/Cache/ContentTypeHandler.php @@ -233,15 +233,6 @@ function () use ($groupId) { ); } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - $this->logger->logCall(__METHOD__, [ - 'query' => $query, - ]); - - return $this->persistenceHandler->contentTypeHandler()->countContentTypes($query); - } - public function findContentTypes(?ContentTypeQuery $query = null): array { $this->logger->logCall(__METHOD__, [ diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway.php b/src/lib/Persistence/Legacy/Content/Type/Gateway.php index 74e80462cc..24a4f65a72 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway.php @@ -173,8 +173,6 @@ abstract public function removeFieldDefinitionTranslation( */ abstract public function removeByUserAndVersion(int $userId, int $version): void; - abstract public function countContentTypes(?ContentTypeQuery $query = null): int; - /** * @return array> */ diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index a7089e6bec..a6bf728f38 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -1417,20 +1417,6 @@ public function removeByUserAndVersion(int $userId, int $version): void } } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - $queryBuilder = $this->connection->createQueryBuilder(); - $queryBuilder - ->select('COUNT(c.id)') - ->from(self::CONTENT_TYPE_TABLE, 'c'); - - if ($query !== null && !empty($query->getCriterion())) { - $queryBuilder->andWhere($this->criterionVisitor->visitCriteria($queryBuilder, $query->getCriterion())); - } - - return (int)$queryBuilder->execute()->fetchOne(); - } - public function findContentTypes(?ContentTypeQuery $query = null): array { $queryBuilder = $this->getLoadTypeQueryBuilder(); diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php index aedca31ecd..9ad58c1a91 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php @@ -343,15 +343,6 @@ public function removeByUserAndVersion(int $userId, int $version): void } } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - try { - return $this->innerGateway->countContentTypes($query); - } catch (DBALException | PDOException $e) { - throw DatabaseException::wrap($e); - } - } - public function findContentTypes(?ContentTypeQuery $query = null): array { try { diff --git a/src/lib/Persistence/Legacy/Content/Type/Handler.php b/src/lib/Persistence/Legacy/Content/Type/Handler.php index 5b7c601419..fd8a40b400 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Type/Handler.php @@ -196,11 +196,6 @@ public function loadContentTypeList(array $contentTypeIds): array ); } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - return $this->contentTypeGateway->countContentTypes($query); - } - public function findContentTypes(?ContentTypeQuery $query = null): array { $rows = $this->contentTypeGateway->findContentTypes($query); diff --git a/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php b/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php index 2c5d948bc4..7693f7ff7c 100644 --- a/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php +++ b/src/lib/Persistence/Legacy/Content/Type/MemoryCachingHandler.php @@ -172,11 +172,6 @@ public function loadContentTypes($groupId, $status = Type::STATUS_DEFINED): arra return $types; } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - return $this->innerHandler->countContentTypes($query); - } - public function findContentTypes(?ContentTypeQuery $query = null): array { return $this->innerHandler->findContentTypes($query); diff --git a/src/lib/Repository/ContentTypeService.php b/src/lib/Repository/ContentTypeService.php index 19bdd9353e..1f38d22e09 100644 --- a/src/lib/Repository/ContentTypeService.php +++ b/src/lib/Repository/ContentTypeService.php @@ -952,11 +952,6 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit ]); } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - return $this->contentTypeHandler->countContentTypes($query); - } - /** * {@inheritdoc} */ diff --git a/src/lib/Repository/SiteAccessAware/ContentTypeService.php b/src/lib/Repository/SiteAccessAware/ContentTypeService.php index bdec60da6b..10377bc5ad 100644 --- a/src/lib/Repository/SiteAccessAware/ContentTypeService.php +++ b/src/lib/Repository/SiteAccessAware/ContentTypeService.php @@ -126,11 +126,6 @@ public function findContentTypes(?ContentTypeQuery $query = null, array $priorit return $this->service->findContentTypes($query, $prioritizedLanguages); } - public function countContentTypes(?ContentTypeQuery $query = null): int - { - return $this->service->countContentTypes($query); - } - public function loadContentTypes(ContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = null): iterable { $prioritizedLanguages = $this->languageResolver->getPrioritizedLanguages($prioritizedLanguages); diff --git a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php deleted file mode 100644 index b9b42b6b28..0000000000 --- a/tests/integration/Core/Repository/ContentTypeService/CountContentTypesTest.php +++ /dev/null @@ -1,139 +0,0 @@ -countContentTypes(); - - $contentTypesObjects = $contentTypeService->findContentTypes(new ContentTypeQuery(null, [], 0, 999)); - - self::assertSame($contentTypesObjects->getTotalCount(), $contentTypesCount); - } - - /** - * @dataProvider dataProviderForTestCount - */ - public function testCountContentTypes(ContentTypeQuery $query, int $expectedCount): void - { - $contentTypeService = self::getContentTypeService(); - - $count = $contentTypeService->countContentTypes($query); - - self::assertSame($expectedCount, $count); - } - - /** - * @return iterable - */ - public function dataProviderForTestCount(): iterable - { - yield 'identifiers' => [ - new ContentTypeQuery( - new ContentTypeIdentifier(['folder', 'article']), - ), - 2, - ]; - - yield 'user group content type' => [ - new ContentTypeQuery( - new ContentTypeGroupId([2]), - ), - 2, - ]; - - yield 'ids' => [ - new ContentTypeQuery( - new ContentTypeId([1]), - ), - 1, - ]; - - yield 'system group' => [ - new ContentTypeQuery( - new IsSystem(false), - ), - 3, - ]; - - yield 'logical and' => [ - new ContentTypeQuery( - new LogicalAnd([ - new ContentTypeIdentifier(['folder', 'article']), - new ContentTypeGroupId([1]), - ]), - ), - 2, - ]; - - yield 'logical or' => [ - new ContentTypeQuery( - new LogicalOr([ - new ContentTypeIdentifier(['folder', 'article']), - new ContentTypeGroupId([2]), - ]), - ), - 4, - ]; - - yield 'logical not resulting in empty set' => [ - new ContentTypeQuery( - new LogicalAnd([ - new LogicalNot([ - new ContentTypeIdentifier(['user', 'user_group']), - ]), - new ContentTypeGroupId([2]), - ]), - ), - 0, - ]; - - yield 'logical not' => [ - new ContentTypeQuery( - new LogicalAnd([ - new LogicalNot([ - new ContentTypeIdentifier(['user']), - ]), - new ContentTypeGroupId([2]), - ]), - ), - 1, - ]; - - yield 'logical or outside with logical and inside' => [ - new ContentTypeQuery( - new LogicalOr([ - new LogicalAnd([ - new ContentTypeIdentifier(['folder', 'article']), - new ContentTypeGroupId([1]), - ]), - new ContentTypeIdentifier(['user']), - ]), - ), - 3, - ]; - } -} From 0859ce5f4821cd042b7370dec84f637601b93fc0 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Sun, 17 Aug 2025 19:43:48 +0200 Subject: [PATCH 12/14] IBX-10458: Fixup --- tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php index 19a1f8556c..0ceeeafef1 100644 --- a/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php +++ b/tests/lib/Repository/SiteAccessAware/ContentTypeServiceTest.php @@ -103,8 +103,6 @@ public function providerForPassTroughMethods() ['deleteUserDrafts', [14], null], ['findContentTypes', [new ContentTypeQuery()], new SearchResult()], - - ['countContentTypes', [new ContentTypeQuery()], 1], ]; } From f81683c8a4205d6edab3aabddde533972a64b443 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 20 Aug 2025 14:37:18 +0200 Subject: [PATCH 13/14] IBX-10458: Fixed count --- .../Type/CriterionHandlerInterface.php | 6 +-- .../Legacy/Content/Type/Gateway.php | 4 +- .../Content/Type/Gateway/DoctrineDatabase.php | 28 ++++++++++++- .../Type/Gateway/ExceptionConversion.php | 9 +++++ .../Legacy/Content/Type/Handler.php | 4 +- .../FindContentTypesTest.php | 40 +++++++++++++++++++ 6 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php b/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php index 348b922905..da78156259 100644 --- a/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php +++ b/src/contracts/Persistence/Content/Type/CriterionHandlerInterface.php @@ -13,17 +13,17 @@ use Ibexa\Core\Persistence\Legacy\Content\Type\Gateway\CriterionVisitor\CriterionVisitor; /** - * @template T of \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface + * @template TCriterion of \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface */ interface CriterionHandlerInterface { /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface $criterion + * @param TCriterion $criterion */ public function supports(CriterionInterface $criterion): bool; /** - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\Query\CriterionInterface $criterion + * @param TCriterion $criterion * * @return string|\Doctrine\DBAL\Query\Expression\CompositeExpression */ diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway.php b/src/lib/Persistence/Legacy/Content/Type/Gateway.php index 24a4f65a72..5d66ed9ffc 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway.php @@ -37,6 +37,8 @@ abstract public function insertGroup(Group $group): int; abstract public function updateGroup(GroupUpdateStruct $group): void; + abstract public function countTypes(): int; + abstract public function countTypesInGroup(int $groupId): int; abstract public function countGroupsForType(int $typeId, int $status): int; @@ -174,7 +176,7 @@ abstract public function removeFieldDefinitionTranslation( abstract public function removeByUserAndVersion(int $userId, int $version): void; /** - * @return array> + * @return array{items: array>, count: int} */ abstract public function findContentTypes(?ContentTypeQuery $query = null): array; } diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php index a6bf728f38..564681873b 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/DoctrineDatabase.php @@ -207,6 +207,16 @@ public function updateGroup(GroupUpdateStruct $group): void $query->execute(); } + public function countTypes(): int + { + $query = $this->connection->createQueryBuilder(); + $query + ->select($this->dbPlatform->getCountExpression('id')) + ->from(self::CONTENT_TYPE_TABLE); + + return (int)$query->execute()->fetchOne(); + } + public function countTypesInGroup(int $groupId): int { $query = $this->connection->createQueryBuilder(); @@ -1419,12 +1429,23 @@ public function removeByUserAndVersion(int $userId, int $version): void public function findContentTypes(?ContentTypeQuery $query = null): array { + $totalCount = $this->countTypes(); + if ($totalCount === 0) { + return [ + 'count' => $totalCount, + 'items' => [], + ]; + } + $queryBuilder = $this->getLoadTypeQueryBuilder(); if ($query === null) { $queryBuilder->setMaxResults(ContentTypeQuery::DEFAULT_LIMIT); - return $queryBuilder->execute()->fetchAllAssociative(); + return [ + 'count' => $totalCount, + 'items' => $queryBuilder->execute()->fetchAllAssociative(), + ]; } if (!empty($query->getCriterion())) { @@ -1442,7 +1463,10 @@ public function findContentTypes(?ContentTypeQuery $query = null): array $queryBuilder->addOrderBy($column, $this->getQuerySortingDirection($sortClause->direction)); } - return $queryBuilder->execute()->fetchAllAssociative(); + return [ + 'count' => $totalCount, + 'items' => $queryBuilder->execute()->fetchAllAssociative(), + ]; } /** diff --git a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php index 9ad58c1a91..75de6188a1 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Type/Gateway/ExceptionConversion.php @@ -59,6 +59,15 @@ public function updateGroup(GroupUpdateStruct $group): void } } + public function countTypes(): int + { + try { + return $this->innerGateway->countTypes(); + } catch (DBALException | PDOException $e) { + throw DatabaseException::wrap($e); + } + } + public function countTypesInGroup(int $groupId): int { try { diff --git a/src/lib/Persistence/Legacy/Content/Type/Handler.php b/src/lib/Persistence/Legacy/Content/Type/Handler.php index fd8a40b400..09ee1bbd5b 100644 --- a/src/lib/Persistence/Legacy/Content/Type/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Type/Handler.php @@ -200,12 +200,12 @@ public function findContentTypes(?ContentTypeQuery $query = null): array { $rows = $this->contentTypeGateway->findContentTypes($query); $items = $this->mapper->extractTypesFromRows( - $rows, + $rows['items'], true ); return [ - 'count' => count($items), + 'count' => $rows['count'], 'items' => $items, ]; } diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index 76f8679cee..890af883f4 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -44,7 +44,18 @@ public function testFindContentTypes(ContentTypeQuery $query, array $expectedIde { $contentTypeService = self::getContentTypeService(); + $expectedCount = $contentTypeService->findContentTypes( + new ContentTypeQuery( + null, + [], + 0, + 0, + ) + ); + $expectedCount = count($expectedCount->getContentTypes()); + $contentTypes = $contentTypeService->findContentTypes($query); + $identifiers = array_map( static fn (ContentType $contentType): string => $contentType->getIdentifier(), $contentTypes->getContentTypes(), @@ -52,6 +63,7 @@ public function testFindContentTypes(ContentTypeQuery $query, array $expectedIde self::assertCount(count($expectedIdentifiers), $identifiers); self::assertEqualsCanonicalizing($expectedIdentifiers, $identifiers); + self::assertSame($expectedCount, $contentTypes->getTotalCount()); } public function testFindContentTypesAscSortedByIdentifier(): void @@ -73,6 +85,34 @@ public function testFindContentTypesAscSortedByIdentifier(): void self::assertSame(['article', 'file', 'folder', 'user'], $identifiers); } + public function testPagination(): void + { + $contentTypeService = self::getContentTypeService(); + + $collectedContentTypeIDs = []; + $pageSize = 10; + $noOfPages = 3; + + for ($offset = 0; $offset < $noOfPages; $offset += $pageSize) { + $searchResult = $contentTypeService->findContentTypes( + new ContentTypeQuery(null, [], $offset, $pageSize), + ); + + // an actual number of items on a current page + self::assertCount($pageSize, $searchResult); + + // check if results are not duplicated across multiple pages + foreach ($searchResult->getContentTypes() as $contentType) { + self::assertNotContains( + $contentType->getIdentifier(), + $collectedContentTypeIDs, + "Content type '{$contentType->getIdentifier()}' exists on multiple pages" + ); + $collectedContentTypeIDs[] = $contentType->getIdentifier(); + } + } + } + public function testFindContentTypesContainingFieldDefinitions(): void { $contentTypeService = self::getContentTypeService(); From 09480fad14602f873c8967c161eb8d7680dafa60 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Wed, 20 Aug 2025 17:02:06 +0200 Subject: [PATCH 14/14] IBX-10458: Fixed pagination test --- .../Repository/ContentTypeService/FindContentTypesTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php index 890af883f4..fdc849928d 100644 --- a/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php +++ b/tests/integration/Core/Repository/ContentTypeService/FindContentTypesTest.php @@ -93,9 +93,10 @@ public function testPagination(): void $pageSize = 10; $noOfPages = 3; - for ($offset = 0; $offset < $noOfPages; $offset += $pageSize) { + for ($page = 1; $page <= $noOfPages; ++$page) { + $offset = ($page - 1) * $pageSize; $searchResult = $contentTypeService->findContentTypes( - new ContentTypeQuery(null, [], $offset, $pageSize), + new ContentTypeQuery(null, [new Identifier()], $offset, $pageSize), ); // an actual number of items on a current page