Skip to content

Commit b211121

Browse files
author
Bertrand Dunogier
committed
Prototyped natural filtering
1 parent 46fa215 commit b211121

File tree

14 files changed

+413
-19
lines changed

14 files changed

+413
-19
lines changed

src/DependencyInjection/EzSystemsEzPlatformGraphQLExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function load(array $configs, ContainerBuilder $container)
3939
$loader->load('services/mutations.yml');
4040
$loader->load('services/resolvers.yml');
4141
$loader->load('services/schema.yml');
42+
$loader->load('services/search.yml');
4243
$loader->load('services/services.yml');
4344
$loader->load('default_settings.yml');
4445
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
namespace EzSystems\EzPlatformGraphQL\DependencyInjection\Factory;
8+
9+
use eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider;
10+
11+
class SearchFeaturesFactory
12+
{
13+
/**
14+
* @var \eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider
15+
*/
16+
private $configurationProvider;
17+
18+
/**
19+
* @var \EzSystems\EzPlatformGraphQL\Search\SearchFeatures[]
20+
*/
21+
private $searchFeatures = [];
22+
23+
public function __construct(RepositoryConfigurationProvider $configurationProvider, array $searchFeatures)
24+
{
25+
$this->configurationProvider = $configurationProvider;
26+
$this->searchFeatures = $searchFeatures;
27+
}
28+
29+
public function build()
30+
{
31+
$searchEngine = $this->configurationProvider->getRepositoryConfig()['search']['engine'];
32+
33+
if (isset($this->searchFeatures[$searchEngine])) {
34+
return $this->searchFeatures[$searchEngine];
35+
} else {
36+
throw new \InvalidArgumentException('Search engine not found');
37+
}
38+
}
39+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
namespace EzSystems\EzPlatformGraphQL\GraphQL\InputMapper;
8+
9+
use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentTypeLoader;
10+
11+
/**
12+
* Pre-processes the input to change fields passed using their identifier to the Field input key.
13+
*/
14+
class FieldsQueryMapper implements QueryMapper
15+
{
16+
/**
17+
* @var QueryMapper
18+
*/
19+
private $innerMapper;
20+
/**
21+
* @var ContentTypeLoader
22+
*/
23+
private $contentTypeLoader;
24+
25+
public function __construct(ContentTypeLoader $contentTypeLoader, QueryMapper $innerMapper)
26+
{
27+
$this->innerMapper = $innerMapper;
28+
$this->contentTypeLoader = $contentTypeLoader;
29+
}
30+
31+
/**
32+
* @param array $inputArray
33+
*
34+
* @return \eZ\Publish\API\Repository\Values\Content\Query
35+
*/
36+
public function mapInputToQuery(array $inputArray)
37+
{
38+
if (isset($inputArray['ContentTypeIdentifier']) && isset($inputArray['fieldsFilters'])) {
39+
$contentType = $this->contentTypeLoader->loadByIdentifier($inputArray['ContentTypeIdentifier']);
40+
$fieldsArgument = [];
41+
42+
foreach ($inputArray['fieldsFilters'] as $fieldDefinitionIdentifier => $value) {
43+
if (($fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier)) === null) {
44+
continue;
45+
}
46+
47+
if (!$fieldDefinition->isSearchable) {
48+
continue;
49+
}
50+
51+
$fieldFilter = $this->buildFieldFilter($fieldDefinitionIdentifier, $value);
52+
if ($fieldFilter !== null) {
53+
$fieldsArgument[] = $fieldFilter;
54+
}
55+
}
56+
57+
$inputArray['Fields'] = $fieldsArgument;
58+
}
59+
60+
return $this->innerMapper->mapInputToQuery($inputArray);
61+
}
62+
63+
private function buildFieldFilter($fieldDefinitionIdentifier, $value)
64+
{
65+
if (is_array($value) && count($value) === 1) {
66+
$value = $value[0];
67+
}
68+
$operator = 'eq';
69+
70+
// @todo if 3 items, and first item is 'between', use next two items as value
71+
if (is_array($value)) {
72+
$operator = 'in';
73+
} elseif (is_string($value)) {
74+
if ($value[0] === '~') {
75+
$operator = 'like';
76+
$value = substr($value, 1);
77+
if (strpos($value, '%') === false) {
78+
$value = "%$value%";
79+
}
80+
} elseif ($value[0] === '<') {
81+
$value = substr($value, 1);
82+
if ($value[0] === '=') {
83+
$operator = 'lte';
84+
$value = substr($value, 2);
85+
} else {
86+
$operator = 'lt';
87+
$value = substr($value, 1);
88+
}
89+
} elseif ($value[0] === '<') {
90+
$value = substr($value, 1);
91+
if ($value[0] === '=') {
92+
$operator = 'gte';
93+
$value = substr($value, 2);
94+
} else {
95+
$operator = 'gt';
96+
$value = substr($value, 1);
97+
}
98+
}
99+
}
100+
101+
return ['target' => $fieldDefinitionIdentifier, $operator => trim($value)];
102+
}
103+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
namespace EzSystems\EzPlatformGraphQL\GraphQL\InputMapper;
8+
9+
interface QueryMapper
10+
{
11+
/**
12+
* @param array $inputArray
13+
*
14+
* @return \eZ\Publish\API\Repository\Values\Content\Query
15+
*/
16+
public function mapInputToQuery(array $inputArray);
17+
}

src/GraphQL/InputMapper/SearchQueryMapper.php

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use eZ\Publish\API\Repository\Values\Content\Query;
1010
use InvalidArgumentException;
1111

12-
class SearchQueryMapper
12+
class SearchQueryMapper implements QueryMapper
1313
{
1414
/**
1515
* @param array $inputArray
@@ -36,19 +36,19 @@ public function mapInputToQuery(array $inputArray)
3636
}
3737

3838
if (isset($inputArray['Field'])) {
39-
if (isset($inputArray['Field']['target'])) {
40-
$criteria[] = $this->mapInputToFieldCriterion($inputArray['Field']);
41-
} else {
42-
$criteria = array_merge(
43-
$criteria,
44-
array_map(
45-
function ($input) {
46-
return $this->mapInputToFieldCriterion($input);
47-
},
48-
$inputArray['Field']
49-
)
50-
);
51-
}
39+
$inputArray['Fields'] = [$inputArray['Field']];
40+
}
41+
42+
if (isset($inputArray['Fields'])) {
43+
$criteria = array_merge(
44+
$criteria,
45+
array_map(
46+
function ($input) {
47+
return $this->mapInputToFieldCriterion($input);
48+
},
49+
$inputArray['Fields']
50+
)
51+
);
5252
}
5353

5454
if (isset($inputArray['ParentLocationId'])) {

src/GraphQL/Resolver/SearchResolver.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace EzSystems\EzPlatformGraphQL\GraphQL\Resolver;
88

99
use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentLoader;
10-
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper;
10+
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper;
1111
use eZ\Publish\API\Repository\SearchService;
1212
use Overblog\GraphQLBundle\Relay\Connection\Paginator;
1313

@@ -22,7 +22,7 @@ class SearchResolver
2222
private $searchService;
2323

2424
/**
25-
* @var SearchQueryMapper
25+
* @var QueryMapper
2626
*/
2727
private $queryMapper;
2828

@@ -31,7 +31,7 @@ class SearchResolver
3131
*/
3232
private $contentLoader;
3333

34-
public function __construct(ContentLoader $contentLoader, SearchService $searchService, SearchQueryMapper $queryMapper)
34+
public function __construct(ContentLoader $contentLoader, SearchService $searchService, QueryMapper $queryMapper)
3535
{
3636
$this->contentLoader = $contentLoader;
3737
$this->searchService = $searchService;
@@ -48,6 +48,7 @@ public function searchContent($args)
4848
public function searchContentOfTypeAsConnection($contentTypeIdentifier, $args)
4949
{
5050
$query = $args['query'] ?: [];
51+
$query['fieldsFilters'] = $args['filter'] ?: [];
5152
$query['ContentTypeIdentifier'] = $contentTypeIdentifier;
5253
$query['sortBy'] = $args['sortBy'];
5354
$query = $this->queryMapper->mapInputToQuery($query);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
services:
2+
_defaults:
3+
autoconfigure: true
4+
autowire: true
5+
public: false
6+
7+
EzSystems\EzPlatformGraphQL\DependencyInjection\Factory\SearchFeaturesFactory:
8+
arguments:
9+
$configurationProvider: '@ezpublish.api.repository_configuration_provider'
10+
$searchFeatures:
11+
solr: '@EzSystems\EzPlatformGraphQL\Search\SolrSearchFeatures'
12+
legacy: '@EzSystems\EzPlatformGraphQL\Search\LegacySearchFeatures'
13+
14+
15+
EzSystems\EzPlatformGraphQL\Search\SearchFeatures:
16+
factory: ['@EzSystems\EzPlatformGraphQL\DependencyInjection\Factory\SearchFeaturesFactory', build]
17+
18+
EzSystems\EzPlatformGraphQL\Search\SolrSearchFeatures: ~
19+
20+
EzSystems\EzPlatformGraphQL\Search\LegacySearchFeatures:
21+
arguments:
22+
$converterRegistry: '@ezpublish.persistence.legacy.field_value_converter.registry'
23+
24+
EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper: '@EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper'
25+
26+
EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: ~
27+
28+
EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\FieldsQueryMapper:
29+
decorates: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper
30+
arguments:
31+
$innerMapper: '@EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\FieldsQueryMapper.inner'

src/Resources/config/services/services.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@ services:
1111
- { name: console.command }
1212

1313
EzSystems\EzPlatformGraphQL\GraphQL\TypeDefinition\ContentTypeMapper: ~
14-
15-
EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: ~

src/Schema/Domain/Content/NameHelper.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ public function fieldDefinitionField(FieldDefinition $fieldDefinition)
9393
return lcfirst($this->toCamelCase($fieldDefinition->identifier));
9494
}
9595

96+
public function filterType(ContentType $contentType)
97+
{
98+
return $this->domainContentName($contentType) . 'Filter';
99+
}
100+
96101
private function toCamelCase($string)
97102
{
98103
return $this->caseConverter->denormalize($string);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
namespace EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Worker\ContentType;
8+
9+
use eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup;
10+
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Worker\BaseWorker;
11+
use EzSystems\EzPlatformGraphQL\Schema\Worker;
12+
use EzSystems\EzPlatformGraphQL\Schema\Builder;
13+
use EzSystems\EzPlatformGraphQL\Schema\Builder\Input;
14+
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
15+
16+
class DefineDomainContentFilter extends BaseWorker implements Worker
17+
{
18+
public function work(Builder $schema, array $args)
19+
{
20+
$schema->addType(new Input\Type($this->filterType($args), 'input-object'));
21+
$schema->addArgToField(
22+
$this->groupType($args),
23+
$this->connectionField($args),
24+
new Input\Arg('filter', $this->filterType($args))
25+
);
26+
}
27+
28+
public function canWork(Builder $schema, array $args)
29+
{
30+
return isset($args['ContentTypeGroup']) && $args['ContentTypeGroup'] instanceof ContentTypeGroup
31+
&& isset($args['ContentType']) && $args['ContentType'] instanceof ContentType
32+
&& !$schema->hasType($this->filterType($args));
33+
}
34+
35+
protected function filterType(array $args): string
36+
{
37+
return $this->getNameHelper()->filterType($args['ContentType']);
38+
}
39+
40+
protected function groupType(array $args): string
41+
{
42+
return $this->getNameHelper()->domainGroupName($args['ContentTypeGroup']);
43+
}
44+
45+
protected function connectionField(array $args): string
46+
{
47+
return $this->getNameHelper()->domainContentCollectionField($args['ContentType']);
48+
}
49+
}

0 commit comments

Comments
 (0)