My setup is Symfony 5 with the latest API-Platform version running on PHP 7.3.
So I would like to be able to query both on name and username (maybe even email).
Do I need to write a custom resolver?
This is what I’ve tried so far but this results in a WHERE name = $name AND username = $name.
query SearchUsers ($name: String!) {
users(name: $name, username: $name) {
edges {
cursor
node {
id
username
email
avatar
}
}
}
}
My entity:
/**
* @ApiResource
* @ApiFilter(SearchFilter::class, properties={
* "name": "ipartial",
* "username": "ipartial",
* "email": "ipartial",
* })
*
* @ORMTable(name="users")
* @ORMEntity(repositoryClass="DomainRepositoryUserRepository")
* @ORMHasLifecycleCallbacks()
*/
class User
{
private $name;
private $username;
private $email;
// ... code omitted ...
}
4
Answers
The
OR
condition in the search filter is not handled by default in API Platform, you need a custom filter to do this (https://api-platform.com/docs/core/filters/#creating-custom-filters).See also: https://github.com/api-platform/core/issues/2400.
I made such a custom filter for chapter 6 of my tutorial. I include its code below.
You can configure which properties it searches in the ApiFilter attribute. In your case that would be:
It splits the search string into words and searches each of the properties case insensitive for each word, so a query string like:
will search in all specified properties both LOWER(..) LIKE ‘%katch%’ OR LOWER(..) LIKE ‘%squash%’
Limitations: It may be limited to string properties (depending on the DB) and it does not sort by relevance.
The code (apip 3.0):
The service needs configuration in api/config/services.yaml
($searchParameterName can actually be configured from the #ApiFilter attribute)
Maybe you want let the clients choose how to combine filter criteria and logic. This can be done by nesting the filter criteria in "and" or "or, like:
This will return all users with "super" in their username OR "john" in their name. Or if you need more complex logic and multiple criteria for the same property:
This will return all users with john in their names AND (microsoft.com or apple.com in their email address). Because of the nesting of or the criteria for the description are combined together through AND with the criterium for name, which must allways be true while only one of the criteria for the email needs to be true for a user to be returned.
To make this work within your app create a file FilterLogic.php in your api src/Filter folder
(create this folder if you don’t have one yet) with the following content:
Then add the following service configuration to your api config/services.yml:
Finally adapt your entity like this:
You can apply it in other classes as well just by adding the @ApiFilter annotation.
Limitations
This version is for Api Platform 3.0 and 2.7 with metadata_backward_compatibility_layer set to false. For older versions see FilterBundle.
For reasons of security Expressions that are nested in "and", "or" or "not" are allways combined with normal
expressions by AND.
Works with built in filters of Api Platform, except for DateFilter with EXCLUDE_NULL.
This DateFilter subclass may fix it.
Assumes that filters create semantically complete expressions in the sense that
expressions added to the QueryBundle through ::andWhere or ::orWhere do not depend
on one another so that the intended logic is not compromised if they are recombined
with the others by either DoctrineORMQueryExprAndx or DoctrineORMQueryExprOrx.
May Fail if a filter uses QueryBuilder::where or ::add.
You are advised to check the code of all custom and third party Filters and
not to combine those that use QueryBuilder::where or ::add with FilterLogic
or that produce complex logic that is not semantically complete. For an
example of semantically complete and incomplete expressions see DateFilterTest.
The built in filters of Api Platform IMHO contain a bug with respect to the JOINs they generate. As a result, combining them with OR does not work as expected with properties nested over to-many and nullable associations. FilterBundle provides workarounds, but they do change the behavior of ExistsFilter =false.
Thanks MetaClass and HasBert, it works perfectly. The code to add the improvment of nested properties