-
Notifications
You must be signed in to change notification settings - Fork 16
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
base: dev
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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).
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. |
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. |
Applied some of the comments in the pr and also in slack. so now we have
Open to more suggestions! :) |
There was a problem hiding this 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'; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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'} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
created the Standard Criterion visitor and removed the unneded ones. i would need some help with the spec now. |
fix ident in yaml
bd4572b
to
4494d2a
Compare
added spec for the query builder and modified the searchQueryMapper spec too. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
public function visit(QueryBuilder $queryBuilder, $value): void | ||
{ | ||
$queryBuilder->setSortBy(array_map( | ||
function ($sortClauseClass) { |
There was a problem hiding this comment.
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.
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 :).