Skip to content

Commit

Permalink
Prototyped natural filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
Bertrand Dunogier committed Aug 31, 2019
1 parent 46fa215 commit b211121
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('services/mutations.yml');
$loader->load('services/resolvers.yml');
$loader->load('services/schema.yml');
$loader->load('services/search.yml');
$loader->load('services/services.yml');
$loader->load('default_settings.yml');
}
Expand Down
39 changes: 39 additions & 0 deletions src/DependencyInjection/Factory/SearchFeaturesFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\EzPlatformGraphQL\DependencyInjection\Factory;

use eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider;

class SearchFeaturesFactory
{
/**
* @var \eZ\Bundle\EzPublishCoreBundle\ApiLoader\RepositoryConfigurationProvider
*/
private $configurationProvider;

/**
* @var \EzSystems\EzPlatformGraphQL\Search\SearchFeatures[]
*/
private $searchFeatures = [];

public function __construct(RepositoryConfigurationProvider $configurationProvider, array $searchFeatures)
{
$this->configurationProvider = $configurationProvider;
$this->searchFeatures = $searchFeatures;
}

public function build()
{
$searchEngine = $this->configurationProvider->getRepositoryConfig()['search']['engine'];

if (isset($this->searchFeatures[$searchEngine])) {
return $this->searchFeatures[$searchEngine];
} else {
throw new \InvalidArgumentException('Search engine not found');
}
}
}
103 changes: 103 additions & 0 deletions src/GraphQL/InputMapper/FieldsQueryMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\EzPlatformGraphQL\GraphQL\InputMapper;

use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentTypeLoader;

/**
* Pre-processes the input to change fields passed using their identifier to the Field input key.
*/
class FieldsQueryMapper implements QueryMapper
{
/**
* @var QueryMapper
*/
private $innerMapper;
/**
* @var ContentTypeLoader
*/
private $contentTypeLoader;

public function __construct(ContentTypeLoader $contentTypeLoader, QueryMapper $innerMapper)
{
$this->innerMapper = $innerMapper;
$this->contentTypeLoader = $contentTypeLoader;
}

/**
* @param array $inputArray
*
* @return \eZ\Publish\API\Repository\Values\Content\Query
*/
public function mapInputToQuery(array $inputArray)
{
if (isset($inputArray['ContentTypeIdentifier']) && isset($inputArray['fieldsFilters'])) {
$contentType = $this->contentTypeLoader->loadByIdentifier($inputArray['ContentTypeIdentifier']);
$fieldsArgument = [];

foreach ($inputArray['fieldsFilters'] as $fieldDefinitionIdentifier => $value) {
if (($fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier)) === null) {
continue;
}

if (!$fieldDefinition->isSearchable) {
continue;
}

$fieldFilter = $this->buildFieldFilter($fieldDefinitionIdentifier, $value);
if ($fieldFilter !== null) {
$fieldsArgument[] = $fieldFilter;
}
}

$inputArray['Fields'] = $fieldsArgument;
}

return $this->innerMapper->mapInputToQuery($inputArray);
}

private function buildFieldFilter($fieldDefinitionIdentifier, $value)
{
if (is_array($value) && count($value) === 1) {
$value = $value[0];
}
$operator = 'eq';

// @todo if 3 items, and first item is 'between', use next two items as value
if (is_array($value)) {
$operator = 'in';
} elseif (is_string($value)) {
if ($value[0] === '~') {
$operator = 'like';
$value = substr($value, 1);
if (strpos($value, '%') === false) {
$value = "%$value%";
}
} elseif ($value[0] === '<') {
$value = substr($value, 1);
if ($value[0] === '=') {
$operator = 'lte';
$value = substr($value, 2);
} else {
$operator = 'lt';
$value = substr($value, 1);
}
} elseif ($value[0] === '<') {
$value = substr($value, 1);
if ($value[0] === '=') {
$operator = 'gte';
$value = substr($value, 2);
} else {
$operator = 'gt';
$value = substr($value, 1);
}
}
}

return ['target' => $fieldDefinitionIdentifier, $operator => trim($value)];
}
}
17 changes: 17 additions & 0 deletions src/GraphQL/InputMapper/QueryMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\EzPlatformGraphQL\GraphQL\InputMapper;

interface QueryMapper
{
/**
* @param array $inputArray
*
* @return \eZ\Publish\API\Repository\Values\Content\Query
*/
public function mapInputToQuery(array $inputArray);
}
28 changes: 14 additions & 14 deletions src/GraphQL/InputMapper/SearchQueryMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use eZ\Publish\API\Repository\Values\Content\Query;
use InvalidArgumentException;

class SearchQueryMapper
class SearchQueryMapper implements QueryMapper
{
/**
* @param array $inputArray
Expand All @@ -36,19 +36,19 @@ public function mapInputToQuery(array $inputArray)
}

if (isset($inputArray['Field'])) {
if (isset($inputArray['Field']['target'])) {
$criteria[] = $this->mapInputToFieldCriterion($inputArray['Field']);
} else {
$criteria = array_merge(
$criteria,
array_map(
function ($input) {
return $this->mapInputToFieldCriterion($input);
},
$inputArray['Field']
)
);
}
$inputArray['Fields'] = [$inputArray['Field']];
}

if (isset($inputArray['Fields'])) {
$criteria = array_merge(
$criteria,
array_map(
function ($input) {
return $this->mapInputToFieldCriterion($input);
},
$inputArray['Fields']
)
);
}

if (isset($inputArray['ParentLocationId'])) {
Expand Down
7 changes: 4 additions & 3 deletions src/GraphQL/Resolver/SearchResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace EzSystems\EzPlatformGraphQL\GraphQL\Resolver;

use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentLoader;
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper;
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper;
use eZ\Publish\API\Repository\SearchService;
use Overblog\GraphQLBundle\Relay\Connection\Paginator;

Expand All @@ -22,7 +22,7 @@ class SearchResolver
private $searchService;

/**
* @var SearchQueryMapper
* @var QueryMapper
*/
private $queryMapper;

Expand All @@ -31,7 +31,7 @@ class SearchResolver
*/
private $contentLoader;

public function __construct(ContentLoader $contentLoader, SearchService $searchService, SearchQueryMapper $queryMapper)
public function __construct(ContentLoader $contentLoader, SearchService $searchService, QueryMapper $queryMapper)
{
$this->contentLoader = $contentLoader;
$this->searchService = $searchService;
Expand All @@ -48,6 +48,7 @@ public function searchContent($args)
public function searchContentOfTypeAsConnection($contentTypeIdentifier, $args)
{
$query = $args['query'] ?: [];
$query['fieldsFilters'] = $args['filter'] ?: [];
$query['ContentTypeIdentifier'] = $contentTypeIdentifier;
$query['sortBy'] = $args['sortBy'];
$query = $this->queryMapper->mapInputToQuery($query);
Expand Down
31 changes: 31 additions & 0 deletions src/Resources/config/services/search.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
services:
_defaults:
autoconfigure: true
autowire: true
public: false

EzSystems\EzPlatformGraphQL\DependencyInjection\Factory\SearchFeaturesFactory:
arguments:
$configurationProvider: '@ezpublish.api.repository_configuration_provider'
$searchFeatures:
solr: '@EzSystems\EzPlatformGraphQL\Search\SolrSearchFeatures'
legacy: '@EzSystems\EzPlatformGraphQL\Search\LegacySearchFeatures'


EzSystems\EzPlatformGraphQL\Search\SearchFeatures:
factory: ['@EzSystems\EzPlatformGraphQL\DependencyInjection\Factory\SearchFeaturesFactory', build]

EzSystems\EzPlatformGraphQL\Search\SolrSearchFeatures: ~

EzSystems\EzPlatformGraphQL\Search\LegacySearchFeatures:
arguments:
$converterRegistry: '@ezpublish.persistence.legacy.field_value_converter.registry'

EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper: '@EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper'

EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: ~

EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\FieldsQueryMapper:
decorates: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper
arguments:
$innerMapper: '@EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\FieldsQueryMapper.inner'
2 changes: 0 additions & 2 deletions src/Resources/config/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,3 @@ services:
- { name: console.command }

EzSystems\EzPlatformGraphQL\GraphQL\TypeDefinition\ContentTypeMapper: ~

EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper: ~
5 changes: 5 additions & 0 deletions src/Schema/Domain/Content/NameHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public function fieldDefinitionField(FieldDefinition $fieldDefinition)
return lcfirst($this->toCamelCase($fieldDefinition->identifier));
}

public function filterType(ContentType $contentType)
{
return $this->domainContentName($contentType) . 'Filter';
}

private function toCamelCase($string)
{
return $this->caseConverter->denormalize($string);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Worker\ContentType;

use eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup;
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Worker\BaseWorker;
use EzSystems\EzPlatformGraphQL\Schema\Worker;
use EzSystems\EzPlatformGraphQL\Schema\Builder;
use EzSystems\EzPlatformGraphQL\Schema\Builder\Input;
use eZ\Publish\API\Repository\Values\ContentType\ContentType;

class DefineDomainContentFilter extends BaseWorker implements Worker
{
public function work(Builder $schema, array $args)
{
$schema->addType(new Input\Type($this->filterType($args), 'input-object'));
$schema->addArgToField(
$this->groupType($args),
$this->connectionField($args),
new Input\Arg('filter', $this->filterType($args))
);
}

public function canWork(Builder $schema, array $args)
{
return isset($args['ContentTypeGroup']) && $args['ContentTypeGroup'] instanceof ContentTypeGroup
&& isset($args['ContentType']) && $args['ContentType'] instanceof ContentType
&& !$schema->hasType($this->filterType($args));
}

protected function filterType(array $args): string
{
return $this->getNameHelper()->filterType($args['ContentType']);
}

protected function groupType(array $args): string
{
return $this->getNameHelper()->domainGroupName($args['ContentTypeGroup']);
}

protected function connectionField(array $args): string
{
return $this->getNameHelper()->domainContentCollectionField($args['ContentType']);
}
}
Loading

0 comments on commit b211121

Please sign in to comment.