diff --git a/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/Search/QueryBuilderSpec.php b/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/Search/QueryBuilderSpec.php new file mode 100644 index 00000000..3998be22 --- /dev/null +++ b/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/Search/QueryBuilderSpec.php @@ -0,0 +1,52 @@ +shouldHaveType(QueryBuilder::class); + } + + function it_builds_a_query_when_no_citerion_is_defined() + { + $query = $this->buildQuery(); + $query->shouldBeAnInstanceOf(Query::class); + } + + function it_builds_a_query_when_only_one_criterion_is_criterion($criterion) + { + $criterion->beADoubleOf(Criterion::class); + $this->addCriterion($criterion); + $query = $this->buildQuery(); + $query->filter->shouldBe($criterion); + } + + function it_builds_a_query_when_more_than_criterion_is_passed($criterion1, $criterion2) + { + $criterion1->beADoubleOf(Criterion::class); + $criterion2->beADoubleOf(Criterion::class); + + $this->addCriterion($criterion1); + $this->addCriterion($criterion2); + + $query = $this->buildQuery(); + + $query->filter->shouldBeAnInstanceOf(Criterion\LogicalAnd::class); + } + + function it_builds_a_query_with_sort_criterias($sortClause) + { + $sortClause->beADoubleOf(Query\SortClause::class); + $this->addSortClause($sortClause); + + $query = $this->buildQuery(); + $query->sortClauses->shouldBeArray(); + } +} diff --git a/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/SearchQueryMapperSpec.php b/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/SearchQueryMapperSpec.php index 5c613961..be4196ef 100644 --- a/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/SearchQueryMapperSpec.php +++ b/spec/EzSystems/EzPlatformGraphQL/GraphQL/InputMapper/SearchQueryMapperSpec.php @@ -2,253 +2,51 @@ namespace spec\EzSystems\EzPlatformGraphQL\GraphQL\InputMapper; +use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryBuilder; +use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryInputVisitor; use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper; -use DateTime; -use eZ\Publish\API\Repository\Values\Content\Query; -use eZ\Publish\Core\REST\Server\Input\Parser\Criterion\Operator; use PhpSpec\ObjectBehavior; +use Prophecy\Argument; class SearchQueryMapperSpec extends ObjectBehavior { - function it_is_initializable() - { - $this->shouldHaveType(SearchQueryMapper::class); - } - - public function it_maps_ContentTypeIdentifier_to_a_ContentTypeIdentifier_criterion() + function let(QueryInputVisitor $visitor1, QueryInputVisitor $visitor2, QueryBuilder $queryBuilder) { - $this->mapInputToQuery(['ContentTypeIdentifier' => ['article']])->shouldFilterByContentType(['article']); + $this->beConstructedWith(['visitor_1' => $visitor1, 'visitor_2' => $visitor2, 'visitor_3' => $visitor2], $queryBuilder); } - public function it_maps_Text_to_a_FullText_criterion() - { - $this - ->mapInputToQuery(['Text' => 'graphql']) - ->shouldFilterByFullText('graphql'); - } - - public function it_maps_Modified_before_to_a_created_lte_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Modified' => ['before' => '1977/05/04']]) - ->shouldFilterByDateModified(Query\Criterion\Operator::LTE, '1977/05/04'); - } - - public function it_maps_Modified_on_to_a_created_eq_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Modified' => ['on' => '1977/05/04']]) - ->shouldFilterByDateModified(Query\Criterion\Operator::EQ, '1977/05/04'); - } - - public function it_maps_Modified_after_to_a_created_gte_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Modified' => ['after' => '1977/05/04']]) - ->shouldFilterByDateModified(Query\Criterion\Operator::GTE, '1977/05/04'); - } - - public function it_maps_Created_before_to_a_created_lte_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Created' => ['before' => '1977/05/04']]) - ->shouldFilterByDateCreated(Query\Criterion\Operator::LTE, '1977/05/04'); - } - - public function it_maps_Created_on_to_a_created_eq_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Created' => ['on' => '1977/05/04']]) - ->shouldFilterByDateCreated(Query\Criterion\Operator::EQ, '1977/05/04'); - } - - public function it_maps_Created_after_to_a_created_gte_DateMetaData_criterion() - { - $this - ->mapInputToQuery(['Created' => ['after' => '1977/05/04']]) - ->shouldFilterByDateCreated(Query\Criterion\Operator::GTE, '1977/05/04'); - } - - function it_maps_Field_to_a_Field_criterion() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'eq' => 'bar']]) - ->shouldFilterByField('target_field'); - } - - function it_maps_Field_target_to_the_Field_criterion_target() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'eq' => 'bar']]) - ->shouldFilterByField('target_field', Query\Criterion\Operator::EQ, 'bar'); - } - - function it_maps_Field_with_value_at_operator_key_to_the_Field_criterion_value() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'eq' => 'bar']]) - ->shouldFilterByField('target_field', null, 'bar'); - } - - function it_maps_Field_operator_eq_to_Field_criterion_operator_EQ() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'eq' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::EQ); - } - - function it_maps_Field_operator_in_to_Field_criterion_operator_IN() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'eq' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::EQ); - } - - function it_maps_Field_operator_lt_to_Field_criterion_operator_LT() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'lt' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::LT); - } - - function it_maps_Field_operator_lte_to_Field_criterion_operator_LTE() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'lte' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::LTE); - } - - function it_maps_Field_operator_gte_to_Field_criterion_operator_GTE() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'gte' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::GTE); - } - - function it_maps_Field_operator_gt_to_Field_criterion_operator_GT() - { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'gt' => 'bar']]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::GT); - } - - function it_maps_Field_operator_between_to_Field_criterion_operator_BETWEEN() + function it_is_initializable() { - $this - ->mapInputToQuery(['Field' => ['target' => 'target_field', 'between' => [10, 20]]]) - ->shouldFilterByFieldWithOperator(Query\Criterion\Operator::BETWEEN); + $this->shouldHaveType(SearchQueryMapper::class); } - public function getMatchers(): array - { - return [ - 'filterByContentType' => function(Query $query, array $contentTypes) { - $criterion = $this->findCriterionInQueryFilter( - $query, - Query\Criterion\ContentTypeIdentifier::class - ); - - if ($criterion === null) { - return false; - } - - return $criterion->value === $contentTypes; - }, - 'filterByFullText' => function(Query $query, $text) { - $criterion = $this->findCriterionInQueryFilter( - $query, - Query\Criterion\FullText::class - ); - - if ($criterion === null) { - return false; - } - - return $criterion->value === $text; - }, - 'filterByDateModified' => function(Query $query, $operator, $date) { - $criterion = $this->findCriterionInQueryFilter($query, Query\Criterion\DateMetadata::class); - - if ($criterion === null) { - return false; - } - - if ($criterion->target !== Query\Criterion\DateMetadata::MODIFIED) { - return false; - } - - return $criterion->operator == $operator - && $criterion->value[0] == strtotime($date); - }, - 'filterByDateCreated' => function(Query $query, $operator, $date) { - $criterion = $this->findCriterionInQueryFilter($query, Query\Criterion\DateMetadata::class); - - if ($criterion === null) { - return false; - } + function it_uses_one_visitor_and_not_the_other( + QueryInputVisitor $visitor1, + QueryInputVisitor $visitor2, + QueryBuilder $queryBuilder + ) { + $queryBuilder->buildQuery()->shouldBeCalledOnce(); - if ($criterion->target !== Query\Criterion\DateMetadata::CREATED) { - return false; - } + $visitor1->visit($queryBuilder, 'a_value')->shouldBeCalled(); + $visitor2->visit()->shouldNotBeCalled(); - return $criterion->operator == $operator - && $criterion->value[0] == strtotime($date); - }, - 'filterByField' => function(Query $query, $field, $operator = null, $value = null) { - $criterion = $this->findCriterionInQueryFilter($query, Query\Criterion\Field::class); - - if ($criterion === null) { - return false; - } - - if ($criterion->target !== $field) { - return false; - } - - if ($operator !== null && $criterion->operator != $operator) { - return false; - } - return ($value === null || $criterion->value == $value); - }, - 'filterByFieldWithOperator' => function(Query $query, $operator) { - $criterion = $this->findCriterionInQueryFilter($query, Query\Criterion\Field::class); - if ($criterion === null) { - return false; - } - - return $criterion->operator == $operator; - } - ]; + $this->mapInputToQuery(['visitor_1' => 'a_value']); } - private function findCriterionInQueryFilter(Query $query, $searchedCriterionClass) - { - if ($query->filter instanceof Query\Criterion\LogicalOperator) { - return $this->findCriterionInCriterion($query->filter, $searchedCriterionClass); - } else { - if ($query->filter instanceof $searchedCriterionClass) { - return $query->filter; - } - } - - return null; - } - - private function findCriterionInCriterion(Query\Criterion\LogicalOperator $logicalCriterion, $searchedCriterionClass) - { - foreach ($logicalCriterion->criteria as $criterion) { - if ($criterion instanceof Query\Criterion\LogicalOperator) { - $criterion = $this->findCriterionInCriterion($criterion, $searchedCriterionClass); - if ($criterion !== null) { - return $criterion; - } - } + function it_visits_same_visitor_more_than_once( + QueryInputVisitor $visitor1, + QueryInputVisitor $visitor2, + QueryBuilder $queryBuilder + ) { + $queryBuilder->buildQuery()->shouldBeCalledOnce(); - if ($criterion instanceof $searchedCriterionClass) { - return $criterion; - } - } + $visitor1->visit($queryBuilder, 'a_value')->shouldBeCalledOnce(); + $visitor2->visit($queryBuilder, Argument::type('string'))->shouldBeCalledTimes(2); - return null; + $this->mapInputToQuery([ + 'visitor_1' => 'a_value', + 'visitor_2' => 'value2', + 'visitor_3' => 'value3' + ]); } } diff --git a/spec/EzSystems/EzPlatformGraphQL/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroupSpec.php b/spec/EzSystems/EzPlatformGraphQL/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroupSpec.php index 0edc0f2d..703edbab 100644 --- a/spec/EzSystems/EzPlatformGraphQL/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroupSpec.php +++ b/spec/EzSystems/EzPlatformGraphQL/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroupSpec.php @@ -89,7 +89,7 @@ function it_adds_a_collection_field_for_the_ContentType_to_the_ContentTypeGroup( self::COLLECTION_FIELD, Argument::allOf( FieldArgArgument::withName('sortBy'), - FieldArgArgument::withType('[SortByOptions]') + FieldArgArgument::withType('SortByOptions∫') ) ) ->shouldBeCalled(); diff --git a/src/DependencyInjection/Compiler/SearchQueryMappersPass.php b/src/DependencyInjection/Compiler/SearchQueryMappersPass.php new file mode 100644 index 00000000..5911f1ea --- /dev/null +++ b/src/DependencyInjection/Compiler/SearchQueryMappersPass.php @@ -0,0 +1,32 @@ +has(SearchQueryMapper::class)) { + return; + } + + $definition = $container->findDefinition(SearchQueryMapper::class); + $criteriaTaggedServices = $container->findTaggedServiceIds('ezplatform_graphql.query_input_visitor'); + + $queryInputVisitors = []; + foreach ($criteriaTaggedServices as $id => $tags) { + foreach ($tags as $tag) { + if (isset($tag['inputKey'])) { + $queryInputVisitors[$tag['inputKey']] = new Reference($id); + } + } + } + + $definition->setArgument('$queryInputVisitors', $queryInputVisitors); + } +} diff --git a/src/DependencyInjection/Compiler/SearchSortClauseMappersPass.php b/src/DependencyInjection/Compiler/SearchSortClauseMappersPass.php new file mode 100644 index 00000000..85bb9ac9 --- /dev/null +++ b/src/DependencyInjection/Compiler/SearchSortClauseMappersPass.php @@ -0,0 +1,33 @@ +has(SortBy::class)) { + return; + } + + $definition = $container->findDefinition(SortBy::class); + $sortClauseTaggedServices = $container->findTaggedServiceIds('ezplatform_graphql.query_sortclause_visitor'); + + $sortClauseVisitors = []; + foreach ($sortClauseTaggedServices as $id => $tags) { + foreach ($tags as $tag) { + if (isset($tag['inputKey'])) { + $sortClauseVisitors[$tag['inputKey']] = new Reference($id); + } + } + } + + $definition->setArgument('$sortClauseVisitors', $sortClauseVisitors); + } + +} diff --git a/src/DependencyInjection/EzSystemsEzPlatformGraphQLExtension.php b/src/DependencyInjection/EzSystemsEzPlatformGraphQLExtension.php index 0e8d3097..288b9edf 100644 --- a/src/DependencyInjection/EzSystemsEzPlatformGraphQLExtension.php +++ b/src/DependencyInjection/EzSystemsEzPlatformGraphQLExtension.php @@ -31,6 +31,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('schema.yml'); $loader->load('resolvers.yml'); $loader->load('services.yml'); + $loader->load('query_input_visitors.yml'); } /** diff --git a/src/Exceptions/InvalidOperatorException.php b/src/Exceptions/InvalidOperatorException.php new file mode 100644 index 00000000..cda32fb6 --- /dev/null +++ b/src/Exceptions/InvalidOperatorException.php @@ -0,0 +1,8 @@ +addCompilerPass(new Compiler\FieldValueBuildersPass()); $container->addCompilerPass(new Compiler\SchemaWorkersPass()); $container->addCompilerPass(new Compiler\SchemaDomainIteratorsPass()); + $container->addCompilerPass(new Compiler\SearchQueryMappersPass()); + $container->addCompilerPass(new Compiler\SearchSortClauseMappersPass()); } } diff --git a/src/GraphQL/InputMapper/Search/Criteria/Created.php b/src/GraphQL/InputMapper/Search/Criteria/Created.php new file mode 100644 index 00000000..869259e8 --- /dev/null +++ b/src/GraphQL/InputMapper/Search/Criteria/Created.php @@ -0,0 +1,16 @@ +visitCriterion($queryBuilder, $value, self::TARGET); + } +} diff --git a/src/GraphQL/InputMapper/Search/Criteria/CriterionClass.php b/src/GraphQL/InputMapper/Search/Criteria/CriterionClass.php new file mode 100644 index 00000000..85b4d0f0 --- /dev/null +++ b/src/GraphQL/InputMapper/Search/Criteria/CriterionClass.php @@ -0,0 +1,24 @@ +criterionClass = $criterionClass; + } + + public function visit(QueryBuilder $queryBuilder, $value): void + { + $queryBuilder->addCriterion(new $this->criterionClass($value)); + } +} diff --git a/src/GraphQL/InputMapper/Search/Criteria/DateMetadata.php b/src/GraphQL/InputMapper/Search/Criteria/DateMetadata.php new file mode 100644 index 00000000..c394cea2 --- /dev/null +++ b/src/GraphQL/InputMapper/Search/Criteria/DateMetadata.php @@ -0,0 +1,32 @@ + Query\Criterion\Operator::EQ, + 'before' => Query\Criterion\Operator::LTE, + 'after' => Query\Criterion\Operator::GTE, + ]; + + foreach ($value as $operator => $dateString) { + if (!isset($dateOperatorsMap[$operator])) { + echo "Not a valid operator\n"; + continue; + } + + $queryBuilder->addCriterion(new Query\Criterion\DateMetadata( + $criterion, + $dateOperatorsMap[$operator], + strtotime($dateString) + )); + } + } +} diff --git a/src/GraphQL/InputMapper/Search/Criteria/Field.php b/src/GraphQL/InputMapper/Search/Criteria/Field.php new file mode 100644 index 00000000..bfff766a --- /dev/null +++ b/src/GraphQL/InputMapper/Search/Criteria/Field.php @@ -0,0 +1,50 @@ +mapInputToFieldCriterion($value); + } else { + $criteria = array_merge( + $criteria, + array_map( + function($input) { + return $this->mapInputToFieldCriterion($input); + }, + $value + ) + ); + } + + foreach ($criteria as $criterion) { + $queryBuilder->addCriterion($criterion); + } + } + + private function mapInputToFieldCriterion($input) + { + $operators = ['in', 'eq', 'like', 'contains', 'between', 'lt', 'lte', 'gt', 'gte']; + foreach ($operators as $opString) { + if (isset($input[$opString])) { + $value = $input[$opString]; + $operator = constant('eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator::' . strtoupper($opString)); + } + } + + if (!isset($operator)) { + throw new InvalidArgumentException("Unspecified operator"); + } + + return new Query\Criterion\Field($input['target'], $operator, $value); + } +} diff --git a/src/GraphQL/InputMapper/Search/Criteria/Modified.php b/src/GraphQL/InputMapper/Search/Criteria/Modified.php new file mode 100644 index 00000000..450d3b1f --- /dev/null +++ b/src/GraphQL/InputMapper/Search/Criteria/Modified.php @@ -0,0 +1,16 @@ +visitCriterion($queryBuilder, $value, self::TARGET); + } +} diff --git a/src/GraphQL/InputMapper/Search/QueryBuilder.php b/src/GraphQL/InputMapper/Search/QueryBuilder.php new file mode 100644 index 00000000..d18a0705 --- /dev/null +++ b/src/GraphQL/InputMapper/Search/QueryBuilder.php @@ -0,0 +1,43 @@ +criteria[] = $criterion; + } + + public function addSortClause(Query\SortClause $sortClause): void + { + $this->sortClauses[] = $sortClause; + } + + public function buildQuery(): Query + { + $query = new Query(); + + if (count($this->criteria) > 0) { + $query->filter = count($this->criteria) === 1 ? $this->criteria[0] : new Query\Criterion\LogicalAnd($this->criteria); + } + + if (count($this->sortClauses) > 0) { + $query->sortClauses = $this->sortClauses; + } + + return $query; + } +} diff --git a/src/GraphQL/InputMapper/Search/QueryInputVisitor.php b/src/GraphQL/InputMapper/Search/QueryInputVisitor.php new file mode 100644 index 00000000..373762cc --- /dev/null +++ b/src/GraphQL/InputMapper/Search/QueryInputVisitor.php @@ -0,0 +1,8 @@ +sortClauseVisitors = $sortClauseVisitors; + } + + public function visit(QueryBuilder $queryBuilder, $value): void + { + foreach ($value as $inputKey => $inputValue) { + if (isset($this->sortClauseVisitors[$inputKey])) { + $this->sortClauseVisitors[$inputKey]->visit($queryBuilder, $inputValue); + } + } + } +} diff --git a/src/GraphQL/InputMapper/Search/SortClauseVisitor.php b/src/GraphQL/InputMapper/Search/SortClauseVisitor.php new file mode 100644 index 00000000..b2f530d4 --- /dev/null +++ b/src/GraphQL/InputMapper/Search/SortClauseVisitor.php @@ -0,0 +1,8 @@ +sortClauseClass = $sortClauseClass; + } + + public function visit(QueryBuilder $queryBuilder, $value): void + { + if (empty($value)) { + $value = Query::SORT_ASC; + } + + $queryBuilder->addSortClause(new $this->sortClauseClass($value)); + } +} diff --git a/src/GraphQL/InputMapper/SearchQueryMapper.php b/src/GraphQL/InputMapper/SearchQueryMapper.php index b1592ec8..e0f5cc6b 100644 --- a/src/GraphQL/InputMapper/SearchQueryMapper.php +++ b/src/GraphQL/InputMapper/SearchQueryMapper.php @@ -1,155 +1,44 @@ mapInputToFieldCriterion($inputArray['Field']); - } else { - $criteria = array_merge( - $criteria, - array_map( - function($input) { - return $this->mapInputToFieldCriterion($input); - }, - $inputArray['Field'] - ) - ); - } - } - - if (isset($inputArray['ParentLocationId'])) { - $criteria[] = new Query\Criterion\ParentLocationId($inputArray['ParentLocationId']); - } - - $criteria = array_merge($criteria, $this->mapDateMetadata($inputArray, 'Modified')); - $criteria = array_merge($criteria, $this->mapDateMetadata($inputArray, 'Created')); - - if (isset($inputArray['sortBy'])) { - $query->sortClauses = array_map( - function ($sortClauseClass) { - /** @var Query\SortClause $lastSortClause */ - static $lastSortClause; - - if ($sortClauseClass === Query::SORT_DESC) { - if (!$lastSortClause instanceof Query\SortClause) { - return null; - } - - $lastSortClause->direction = $sortClauseClass; - return null; - } - - if (!class_exists($sortClauseClass)) { - return null; - } - - if (!in_array(Query\SortClause::class, class_parents($sortClauseClass))) { - return null; - } - - return $lastSortClause = new $sortClauseClass; - }, - $inputArray['sortBy'] - ); - // remove null entries left out because of sort direction - $query->sortClauses = array_filter($query->sortClauses); - } - - if (count($criteria) === 0) { - return $query; - } - - if (count($criteria)) { - $query->filter = count($criteria) > 1 ? new Query\Criterion\LogicalAnd($criteria) : $criteria[0]; - } + private $queryInputVisitors; - return $query; + /** + * @var QueryBuilder + */ + private $queryBuilder; + + public function __construct( + array $queryInputVisitors, + QueryBuilder $queryBuilder + ) { + $this->queryInputVisitors = $queryInputVisitors; + $this->queryBuilder = $queryBuilder; } /** - * @param array $queryArg - * @param $dateMetadata - * @return \eZ\Publish\API\Repository\Values\Content\Query\Criterion\DateMetadata[] + * @param array $inputArray + * @return Query */ - private function mapDateMetadata(array $queryArg = [], $dateMetadata) + public function mapInputToQuery(array $inputArray) : Query { - if (!isset($queryArg[$dateMetadata]) || !is_array($queryArg[$dateMetadata])) { - return []; - } - - $targetMap = [ - 'Created' => Query\Criterion\DateMetadata::CREATED, - 'Modified' => Query\Criterion\DateMetadata::MODIFIED, - ]; - - if (!isset($targetMap[$dateMetadata])) { - return []; - } - - $dateOperatorsMap = [ - 'on' => Query\Criterion\Operator::EQ, - 'before' => Query\Criterion\Operator::LTE, - 'after' => Query\Criterion\Operator::GTE, - ]; - - $criteria = []; - foreach ($queryArg[$dateMetadata] as $operator => $dateString) { - if (!isset($dateOperatorsMap[$operator])) { - continue; + foreach ($inputArray as $inputField => $inputValue) { + if (isset($this->queryInputVisitors[$inputField])) { + $this->queryInputVisitors[$inputField]->visit($this->queryBuilder, $inputValue); } - - $criteria[] = new Query\Criterion\DateMetadata( - $targetMap[$dateMetadata], - $dateOperatorsMap[$operator], - strtotime($dateString) - ); } - return $criteria; + return $this->queryBuilder->buildQuery(); } - - private function mapInputToFieldCriterion($input) - { - $operators = ['in', 'eq', 'like', 'contains', 'between', 'lt', 'lte', 'gt', 'gte']; - foreach ($operators as $opString) { - if (isset($input[$opString])) { - $value = $input[$opString]; - $operator = constant('eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator::' . strtoupper($opString)); - } - } - - if (!isset($operator)) { - throw new InvalidArgumentException("Unspecified operator"); - } - - return new Query\Criterion\Field($input['target'], $operator, $value); } } diff --git a/src/Resources/config/graphql/Search.types.yml b/src/Resources/config/graphql/Search.types.yml index 0ce5ddb1..f72a2ff3 100644 --- a/src/Resources/config/graphql/Search.types.yml +++ b/src/Resources/config/graphql/Search.types.yml @@ -84,27 +84,24 @@ DateCriterionOperator: after: {} SortByOptions: - type: enum + type: "input-object" config: - values: - _contentId: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId' + fields: + ContentId: + type: "String" description: "Sort by content id" - _name: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName' + Name: + type: "String" description: "Sort by content name" - _dateModified: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified' + DateModified: + type: "String" description: "Sort by last modification date" - _datePublished: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished' + DatePublished: + type: "String" description: "Sort by initial publication date" - _sectionIdentifier: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier' + SectionIdentifier: + type: "String" description: "Sort by content section identifier" - _sectionName: - value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName' + SectionName: + type: "String" description: "Sort by section name" - _desc: - value: 'descending' - description: "Reverse the previous sorting option" diff --git a/src/Resources/config/query_input_visitors.yml b/src/Resources/config/query_input_visitors.yml new file mode 100644 index 00000000..b52542f2 --- /dev/null +++ b/src/Resources/config/query_input_visitors.yml @@ -0,0 +1,79 @@ +services: + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\ContentTypeId: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\CriterionClass + arguments: + $criterionClass: 'eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeId' + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'ContentTypeId'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\Text: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\CriterionClass + arguments: + $criterionClass: 'eZ\Publish\API\Repository\Values\Content\Query\Criterion\Fulltext' + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'Text'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\ParentLocationId: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\CriterionClass + arguments: + $criterionClass: 'eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId' + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'ParentLocationId'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\Created: + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'Created'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\Modified: + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'Modified'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criteria\Field: + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'Field'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortBy: + tags: + - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'sortBy'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\ContentName: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'Name'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\ContentId: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'ContentId'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\DateModified: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'DateModified'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\DatePublished: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'DatePublished'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SectionIdentifier: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'SectionIdentifier'} + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SectionName: + class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\SortClauses\SortClauseClass + arguments: + $sortClauseClass: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName' + tags: + - {name: 'ezplatform_graphql.query_sortclause_visitor', inputKey: 'SectionName'} diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 297d9b54..d796a93a 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -19,4 +19,8 @@ services: - { name: "overblog_graphql.mutation", alias: "CreateSection", method: "createSection" } - { name: "overblog_graphql.mutation", alias: "DeleteSection", method: "deleteSection" } - EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: ~ + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryBuilder: ~ + + EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: + arguments: + $queryBuilder: '@EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryBuilder' diff --git a/src/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroup.php b/src/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroup.php index bd32f6fb..f4db2047 100644 --- a/src/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroup.php +++ b/src/Schema/Domain/Content/Worker/ContentType/AddDomainContentCollectionToDomainGroup.php @@ -33,7 +33,7 @@ public function work(Builder $schema, array $args) )); $schema->addArgToField($this->groupName($args), $this->typeCollectionField($args), new Input\Arg( - 'sortBy', '[SortByOptions]', + 'sortBy', 'SortByOptions', ['description' => "A sort clause, or array of clauses. Add _desc after a clause to reverse it"] )); } @@ -62,4 +62,4 @@ protected function typeName($args): string { return $this->getNameHelper()->domainContentName($args['ContentType']); } -} \ No newline at end of file +}