Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Natural filtering #5

Open
wants to merge 3 commits into
base: 1.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
}
}
}
25 changes: 19 additions & 6 deletions src/GraphQL/DataLoader/CachedContentTypeLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ class CachedContentTypeLoader implements ContentTypeLoader
*/
private $loadedItems = [];

/**
* @var array
*/
private $identifierToIdMap = [];

/**
* @var ContentType[]
*/
private $loadedItemsByIdentifier = [];

public function __construct(ContentTypeLoader $innerLoader)
{
$this->innerLoader = $innerLoader;
Expand All @@ -31,20 +41,23 @@ public function __construct(ContentTypeLoader $innerLoader)
public function load($contentTypeId): ContentType
{
if (!isset($this->loadedItems[$contentTypeId])) {
$this->loadedItems[$contentTypeId] = $this->innerLoader->load($contentTypeId);
$contentType = $this->innerLoader->load($contentTypeId);
$this->loadedItems[$contentTypeId] = $contentType;
$this->identifierToIdMap[$contentType->identifier] = $contentTypeId;
}

return $this->loadedItems[$contentTypeId];
}

public function loadByIdentifier($identifier): ContentType
{
$contentType = $this->innerLoader->loadByIdentifier($identifier);

if (!isset($this->innerLoader[$contentType->id])) {
$this->innerLoader[$contentType->id] = $contentType;
if (!isset($this->identifierToIdMap[$identifier])) {
$contentType = $this->innerLoader->loadByIdentifier($identifier);
$this->loadedItems[$contentType->id] = $contentType;
$this->identifierToIdMap[$identifier] = $contentType->id;
}

return $contentType;

return $this->loadedItems[$this->identifierToIdMap[$identifier]];
}
}
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
6 changes: 3 additions & 3 deletions src/GraphQL/Resolver/DomainContentResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentLoader;
use EzSystems\EzPlatformGraphQL\GraphQL\DataLoader\ContentTypeLoader;
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper;
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper;
use eZ\Publish\API\Repository\Repository;
use eZ\Publish\Core\FieldType;
use eZ\Publish\API\Repository\Values\Content\Content;
Expand All @@ -31,7 +31,7 @@ class DomainContentResolver
private $typeResolver;

/**
* @var SearchQueryMapper
* @var \EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\QueryMapper
*/
private $queryMapper;

Expand All @@ -53,7 +53,7 @@ class DomainContentResolver
public function __construct(
Repository $repository,
TypeResolver $typeResolver,
SearchQueryMapper $queryMapper,
QueryMapper $queryMapper,
ContentLoader $contentLoader,
ContentTypeLoader $contentTypeLoader)
{
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
Loading