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
docs(adr): filtering system and query parameters #5995
Conversation
Starting this i great, i've been thinking about it and have some ideas, but couldn't find the time...here are some quick unorganized thoughts : The direction i wanted to go was to first implement an AST-based filter (independant from the actual query language), then specific filters for one or more query language which would just translate the query into an AST (I started looking at Symfony's Expression Language but anything would work). It would need to be broken into quite a lot of smaller parts (further thinking needed here ), for example dealing with individual operations ( = / < > ...), which could provide extension points, allowing devs to add their custom operations... I think in the new system i'd be better to force explicit enabling of filtering on properties, and if the target property is a resource, only the IRI should be expected (explicitly filtering on resource.id would still be possible after all...) : you would either have That's about it for my thoughts on it as of right now! |
Most of my concerns and all the override, decorating and tweaking I encountered so far is due to the lack of proper definition of parameters. As you may know, there is no specification for filters because it's a server-side concern from the OpenApi standpoint. But parameters are quite well described and spec'ed up. with this remark I would introduce a parameter attribute to allow to describe properly what is expected. This may resolve most of my concerns:
Each parameter could be linked to one or more "filters" regardless of what is supposed to (filter, sort, paginate...) From the DX standpoint, I think it would be appreciable to be able to rely on the php type system (like BackedEnums for example) to properly describe parameters. A parameter could be declare lke this: #[Parameter('page', processors: [PaginationFilter::class])]
#[Parameter('limit', processors: [PaginationFilter::class])]
class Book
{
#[\Parameter(filters: [SearchFilter::class]]
#[\Parameter(name: 'color', filters: [SearchFilter::class])] // You're welcome 🇺🇸 !
public ColourEnum $colour;
} This could allow to introduce parameter parsers too, allowing to use some sort of expression like a range expression, negation filters, lucene query syntax... As a bonus, I think it could avoid the breaking changes because it's a entirely new concept and I can imagine with a few tweaks we could use the two systems together, allowing the end users to choose which implementation is the best fit for his case. |
Having the possibility to specify the types / validation method for parameters would be a very nice improvement! I'd still keep it inside the filter definition though, something that could look like this : #[ApiFilter(SomeFilter::class, properties:[
'exposedName'=> new FilterProperty(
target: 'realPropertyPath',
type: Type::BUILTIN_TYPE_STRING,
parser: self:nameParser(...),
validation: self::nameValidator(...),
)
]
)] |
We should not have a strong link between a filter (parameter) and a property. |
well there needs to be a way to specify this some way or other, or we can't build the ORM query, can we? Or am i missing the point? |
Because it's locking filters to behave like a parameter is always tight to a property which isn't the case. Something preferable would be:
|
Then where do you see the link between the "exposedName" query parameter and the "realPropertyPath" being made? I'm a bit confused here. |
This is why I have a |
I think I'm actually seing things very differently from what you envision, which explains how i have trouble wrapping my head around the logic of it. #[ApiFilter(NewSearchFilter::class,
properties : [
'name' => new FilterProperty(
target: 'searchableName',
type : Type::BUILTIN_TYPE_STRING,
parser: NameStringSearchFormatter::class
),
'linkedResource' => new FilterProperty(
target: 'otherResourceCollection',
type : Type::BUILTIN_TYPE_STRING,
parser: IriToOtherResourceConverter::class
)],
queryLanguageParser: new ExpressionLanguageParser(),
parameterName: 'query'
)]
class SomeResource
{
public int $id;
public string $name;
public string $searchableName;
public array $otherResourceCollection;
}
class NewSearchFilter
{
function __construct(protected array $properties, protected QueryLanguageParserInterface $queryLanguageParser, protected string $parameterName)
{
}
function filter($property, $value, QueryBuilder $queryBuilder, ...)
{
if($property !== $this->parameterName){
return;
}
$ast = $this->queryLanguageParser->parse($value, $this->properties);
return (new AstFilter($ast))->filter($queryBuilder,...);
}
} The FilterProperty could also have a link property instead of the target, but I feel like it would/will be quite a headache to implement - the correct way to establish the link on the ORM depends on the "state" of the AST (see the code i sent you on slack, typically you're doing simple joins or left joins depending on what happened before). |
...and i'm actually only talking about search filter when you're talking about all filters, which can't help 😅 |
Not a problem, in fact I want to cover most of the possibilities and doing a search filter like you have in mind should be possible but there are some things that aren't portable in your example. For example For API Platform core, we have a SearchFilter, but I'd like to improve the current search filter by splitting the logic, mainly to resolve typing issues for example our current search filter does:
In my opinion, we should find a way to compose with filters working on a single parameter ( |
I think the I am still advocating to split the logic between parameter concept and filters. The nature of the value of the parameter can in some case use a filter or an other, for exemple
can be translated to : Declaring this type of filter using a Filter centric approche is quite cumbersome. Does it make sense ? |
indeed, that was a mistake - i intended something like
That's where the explicit typing would be very useful, yes, with some kind of parser/transformer specified to transform the text representation from the query into what we actually need (that's what i intended with the "parser" parameter). Exact match/starts with/... would be different operators in the AST, which would make it fairly easy to have separate logic for them.
If I understand what you mean correctly, the second one is easily translated into the first one -
Then you'd just add some |
the "query" in "queryLanguageParser" was not meant for how the parameter is passed, but to specify which "query language" is to be used, if several are supported.
I must be missing something...what other uses will the parameters have? If they're only used as filter parameters i'd rather have them tied to the filters that use them, this way one look is enough to know what a filter can do. It feels like better DX to me, but i've been known not to have the most popular opinion on such things... ;)
To me it should be a specific operation, there is no clear indication as to whether a date without time should match any time for that date, or just that date at 00:00:00, or...? |
docs/adr/0006-filters.md
Outdated
} | ||
``` | ||
|
||
3. Validate parameters through the QueryParameterValidator. |
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 renamed the component here #6080
3. Validate parameters through the QueryParameterValidator. | |
3. Validate parameters through the ParameterValidator component (formerly known as QueryParameterValidator). |
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.
Good work!
docs/adr/0006-filters.md
Outdated
public OpenApi\Parameter $openApi; | ||
public string|callable provider(): Operation; | ||
// filter service id | ||
public string $filter; |
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 could we allow inline filter definitions?
public string $filter; | |
public string|callable $filter; |
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 thought about that but with which interface? at first I wanted something like mixed $value, Parameter $parameter, array $context
but it doesn't ally with FilterInterface
bc42359
to
419504d
Compare
Co-authored-by: Vincent <407859+vincentchalamon@users.noreply.github.com>
Introduction of an ADR, few things must be discussed, I joined a few ideas we collected over the past few years. This must be seen as a research into providing better developer experience and more freedom when expressing filters with API Platform.
Friendly ping: @aegypius, @mrossard, @metaclass-nl, @helyakin, @jdeniau, @bendavies, @bpolaszek
if you have ideas, things we should think of, or opinions I'd be glad to hear them, thanks!