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

Refactor query mapper #8

Open
wants to merge 27 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0dbc81e
add compiler pass for search query mappers
crevillo Jan 26, 2019
ffa7bf7
add dedicated file for search query mappers and load it
crevillo Jan 26, 2019
cb3f444
define interface for searchCriterion and first of them
crevillo Jan 26, 2019
83bf501
create rest of the current available search criterions
crevillo Jan 26, 2019
8f0ad91
rename method name
crevillo Jan 26, 2019
4639965
add method to check if inputField can be mapped to a criteria
crevillo Jan 26, 2019
4b19c66
extract sortByPart logic to dedicate method
crevillo Jan 26, 2019
2279ce2
update namespaces after conflict resolution
crevillo Jan 28, 2019
a0925a7
add dedicated file for search query mappers and load it
crevillo Jan 26, 2019
6d9b245
define interface for searchCriterion and first of them
crevillo Jan 26, 2019
75ec18d
create rest of the current available search criterions
crevillo Jan 26, 2019
594c2a0
rename method name
crevillo Jan 26, 2019
832969c
updated namespaces after conflict
crevillo Jan 28, 2019
8ed08be
some typehints
crevillo Jan 28, 2019
eea3c5c
rename file with the visitors
crevillo Jan 28, 2019
05aa445
add querybuilder and adapt all the visitors to the new interface
crevillo Jan 29, 2019
cd4cb88
use "class" constant instead of string
crevillo Feb 1, 2019
a0b555e
remove unneded var
crevillo Feb 1, 2019
4494d2a
add basic criterion visitor
crevillo Feb 4, 2019
e6fab24
spec for querybuilder and searchQueryMapper
crevillo Feb 24, 2019
7184393
deleted unneded folder
crevillo Feb 25, 2019
37dcd75
cs
crevillo Feb 25, 2019
acc7834
spec for querybuilder and searchQueryMapper
crevillo Feb 26, 2019
7b22af1
wip
crevillo Feb 27, 2019
d4a0b70
wip
crevillo Mar 2, 2019
4cf468b
Merge branch 'refactor-query-mapper' of github.com:crevillo/ezplatfor…
crevillo Mar 2, 2019
af2fbf6
refactor to have visitors for sort clauses
crevillo Mar 3, 2019
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
@@ -0,0 +1,52 @@
<?php

namespace spec\EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search;

use eZ\Publish\API\Repository\Values\Content\Query;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion;
use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryBuilder;
use PhpSpec\ObjectBehavior;

class QueryBuilderSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->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($sortBy)
{
$sortBy->beADoubleOf(Query\SortClause::class);
$this->setSortBy($sortBy);

$query = $this->buildQuery();
$query->sortClauses->shouldBeArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]);
}
}
34 changes: 34 additions & 0 deletions src/DependencyInjection/Compiler/SearchQueryMappersPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace EzSystems\EzPlatformGraphQL\DependencyInjection\Compiler;

use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class SearchQueryMappersPass implements CompilerPassInterface
{
const ID = SearchQueryMapper::class;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually wouldn't use a class constant for that. It doesn't deduplicate much, and does so at the cost of readability. I'd use SearchQueryMapper::class directly inside the method.


public function process(ContainerBuilder $container)
{
if (!$container->has(self::ID)) {
return;
}

$definition = $container->findDefinition(self::ID);
$taggedServices = $container->findTaggedServiceIds('ezplatform_graphql.query_input_visitor');

$queryInputVisitors = [];
foreach ($taggedServices as $id => $tags) {
foreach ($tags as $tag) {
if (isset($tag['inputKey'])) {
$queryInputVisitors[$tag['inputKey']] = new Reference($id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the advantage of this is that a query input visitor can be overridden using the key. Useful.

}
}
}

$definition->setArgument('$queryInputVisitors', $queryInputVisitors);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/EzSystemsEzPlatformGraphQLBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new Compiler\FieldValueBuildersPass());
$container->addCompilerPass(new Compiler\SchemaWorkersPass());
$container->addCompilerPass(new Compiler\SchemaDomainIteratorsPass());
$container->addCompilerPass(new Compiler\SearchQueryMappersPass());
}
}
10 changes: 10 additions & 0 deletions src/GraphQL/InputMapper/Search/Criterion/Created.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion;

use eZ\Publish\API\Repository\Values\Content\Query;

class Created extends DateMetadata
{
const TARGET = Query\Criterion\DateMetadata::CREATED;
}
Loading