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

Conversation

crevillo
Copy link
Contributor

Prof of concept here.
It refactors the SearchQueryMapper to allow the extension of those criterios without the need to remodify that class again and again.
Still working on it, but i guess pr is the way to know if i'm the right track :).

Copy link
Member

@bdunogier bdunogier left a comment

Choose a reason for hiding this comment

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

Very good start.

As discussed on Slack, though, it would be better to handle both criteria and sortclauses using QueryInputVisitor objects : they get both the value AND a Query object, and add whatever they're in charge of to the Query.

Can you give it a try ?

And last thing: please base your PR on the dev branch, as it has the renamed namespace EzSystems\EzPlatformGraphQL. It will be merged to master very soon anyway (probably today).

GraphQL/InputMapper/SearchQueryMapper.php Outdated Show resolved Hide resolved
GraphQL/InputMapper/SearchQueryMapper.php Outdated Show resolved Hide resolved
GraphQL/InputMapper/SearchQueryMapper.php Outdated Show resolved Hide resolved
Resources/config/search_query_mappers.yml Outdated Show resolved Hide resolved
@crevillo
Copy link
Contributor Author

thanks for the comments. they all make sense. yes, i was not totally happy either, but i think i will be more happy once i try to work on your suggestions.
Talk soon.

@bdunogier
Copy link
Member

Remember that we take an iterative approach. We can break BC as long as we haven't hit v1.0. The refactoring of this input mapper is necessary in any case, and the general direction is a good one. Keep it coming :)

One thing that may help designing good code, since you're into DDD these days, is to write PhpSpec tests for the input mapper & visitors. They make bad code difficult to test, and push you towards good design. It would be a good opportunity to add a new skill to your portfolio.

@crevillo crevillo changed the base branch from master to dev January 28, 2019 18:06
@crevillo
Copy link
Contributor Author

Applied some of the comments in the pr and also in slack. so now we have

  • the query mapper uses a query builder
  • the query builder has an addCriterion method. Visitors will add criterions to the querybuilder and also can set SortBy.
  • the query Builder has also a buildQuery method that returns a Content Query.

Open to more suggestions! :)

Copy link
Member

@bdunogier bdunogier left a comment

Choose a reason for hiding this comment

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

I think we're getting there. We could go further by generating the definition of ContentSearchQuery from the registered visitors, as it would make this fully extensible.

My only concern is how this works out with #5.


class SearchQueryMappersPass implements CompilerPassInterface
{
const ID = 'EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\SearchQueryMapper';
Copy link
Member

Choose a reason for hiding this comment

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

I would probably remove the constant, and use SearchQueryMapper::class in the if below. Even more so since you use it right after ;)

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.

services:
EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\ContentTypeIdentifier:
tags:
- {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'ContentTypeIdentifier'}
Copy link
Member

Choose a reason for hiding this comment

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

We could think of basing the definition of the ContentSearchQuery on the defined inputKey.

What it implies:

  • add a DomainIterator that iterates over registered criterion input visitors
  • add a worker that adds a field to ContentSearchQuery for each of them

The only remaining question is where we get the type of each field from.

Copy link
Member

Choose a reason for hiding this comment

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

In this regard, we should keep in mind that we would ideally need an input-object (like ContentSearchQuery) that does NOT have Content Type based fields: when filtering on a given content-type, they can't be used (well, they can, but then we will search for content that is of two types, and we don't have that :)

{
public function visit(QueryBuilder $queryBuilder, $value): void
{
$queryBuilder->addCriterion(new Query\Criterion\ParentLocationId($value));
Copy link
Member

Choose a reason for hiding this comment

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

Should we refactor all visitors that instanciate a Criterion object to a generic one ? It could take a criterion class as a constructor argument. We could remove a few duplicate files with this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but Field and DateMetadata seems to work a bit different than the others, aren't they?

Copy link
Member

Choose a reason for hiding this comment

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

Indeed !

But you could at least remove ContentTypeId, ContentTypeIdentifier, ParentLocationId and Text. Furthermore, there are more criteria that work the same way, we could easily add them if we have a class for it.

BasicCriterionVisitor ? Standard ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i don't know if i follow you here.

do you mean having a Standard criterion visitor tagged with allo those input keys?
my doubt is, how to instatiate this standard criterion and tell it to use one criterion or another depending on the inputkey ?

Copy link
Member

Choose a reason for hiding this comment

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

An example:

class BasicCriterionVisitor implements QueryInputVisitor
{
    public function __construct($criterionClass)
    {
        $this->criterionClass = $criterionClass;
    }

    public function visit(QueryBuilder $queryBuilder, $value): void
    {
        $queryBuilder->addCriterion(new {$this->criterionClass}($value));
services:
    EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\ContentTypeId:
        class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\Basic
        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\Criterion\ContentTypeIdentifier:
        class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\Basic
        arguments:
            $criterionClass: 'eZ\Publish\API\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier'
        tags:
            - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'ContentTypeIdentifier'}    

EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\ParentLocationId:
        class: EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\Criterion\Basic
        arguments:
            $criterionClass: 'eZ\Publish\API\Repository\Values\Content\Query\Criterion\ParentLocationId'
        tags:
            - {name: 'ezplatform_graphql.query_input_visitor', inputKey: 'ParentLocationId'}    

Does it make more sense ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, much more clear now. i thought you were trying to avoid all those service definitions, but you're only trying to avoid php classes :)
i'll work on this.

@crevillo
Copy link
Contributor Author

crevillo commented Feb 4, 2019

created the Standard Criterion visitor and removed the unneded ones. i would need some help with the spec now.

@crevillo
Copy link
Contributor Author

added spec for the query builder and modified the searchQueryMapper spec too.

Copy link
Member

@bdunogier bdunogier left a comment

Choose a reason for hiding this comment

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

We are getting there, I think :)


use EzSystems\EzPlatformGraphQL\GraphQL\InputMapper\Search\QueryBuilder;

class SortBy implements QueryInputVisitor
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to refactor this with a SortClauseVisitor, like you did for Criteria ? There are other sort clauses we may wanna support that won't work with this generic code.

Copy link
Contributor Author

@crevillo crevillo Feb 26, 2019

Choose a reason for hiding this comment

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

but currently SortBy are at "the same level" as criterion when building queries with the graphql syntax, right? so, probably, sortby should still be a query input visitor and then we could have sort clauses visitors that could add sortclauses to the query builder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bdunogier i think it makes sense what you suggest, but let me ask if i'm in the right direction. currently we have this in the Search.types.yml file.

SortByOptions:
    type: enum
    config:
        values:
            _contentId:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId'
                description: "Sort by content id"
            _name:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName'
                description: "Sort by content name"
            _dateModified:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified'
                description: "Sort by last modification date"
            _datePublished:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished'
                description: "Sort by initial publication date"
            _sectionIdentifier:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier'
                description: "Sort by content section identifier"
            _sectionName:
                value: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName'
                description: "Sort by section name"
            _desc:
                value: 'descending'
                description: "Reverse the previous sorting option"

Do you mean changes definition there to have it more similar to the ContentSearchQuery one? n (i mean, not map to class directly in the yml)

GraphQL/InputMapper/Search/Criterion/DateMetadata.php Outdated Show resolved Hide resolved
GraphQL/InputMapper/Search/Criterion/DateMetadata.php Outdated Show resolved Hide resolved
GraphQL/InputMapper/Search/Criterion/Text.php Outdated Show resolved Hide resolved
src/GraphQL/InputMapper/Search/Criterion/Standard.php Outdated Show resolved Hide resolved
src/GraphQL/InputMapper/Search/Criterion/DateMetadata.php Outdated Show resolved Hide resolved
src/GraphQL/InputMapper/Search/QueryBuilder.php Outdated Show resolved Hide resolved
src/GraphQL/InputMapper/Search/QueryBuilder.php Outdated Show resolved Hide resolved
src/GraphQL/InputMapper/Search/QueryBuilder.php Outdated Show resolved Hide resolved
public function visit(QueryBuilder $queryBuilder, $value): void
{
$queryBuilder->setSortBy(array_map(
function ($sortClauseClass) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could move that method to a class method ? It made sense to have it inline in the original implementation, but now we can make this a bit cleaner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants