Lighthouse: [Feat] Argument filters

Created on 13 Apr 2018  路  26Comments  路  Source: nuwave/lighthouse

I can't figure out how we can set a field to be filterable.
For example i have some users and I would like to make the name something i can filter by.

Most helpful comment

Hey @olivernybroe,

If you trying to filter something that is using the @belongsTo, @hasMany or @paginate directives you could just use the scopes argument like so and it will pass the field's arguments to your scope.

type User {
  posts(active: Boolean): [Post!]! @hasMany(scopes: ["active"])
}
class Post extends Model
{
    public function scopeActive($query, array $args)
    {
        return $query->when(!empty($args['active']), function ($q) use ($active) {
            return $q->where('active', $args['active']);
        });
    }
}

Otherwise, you just create an argument on the field and use it in your resolver

type User {
  avatar(size: String!): String @field(resolver: "App\\Http\\GraphQL\\UserType@avatar")
}
class UserType
{
    public function avatar($root, array $args)
    {
        // Here the $root could be your User model (or whatever you assigned to the User type)
        return $root->avatar($args['size']);
    }
}

All 26 comments

Hey @olivernybroe,

If you trying to filter something that is using the @belongsTo, @hasMany or @paginate directives you could just use the scopes argument like so and it will pass the field's arguments to your scope.

type User {
  posts(active: Boolean): [Post!]! @hasMany(scopes: ["active"])
}
class Post extends Model
{
    public function scopeActive($query, array $args)
    {
        return $query->when(!empty($args['active']), function ($q) use ($active) {
            return $q->where('active', $args['active']);
        });
    }
}

Otherwise, you just create an argument on the field and use it in your resolver

type User {
  avatar(size: String!): String @field(resolver: "App\\Http\\GraphQL\\UserType@avatar")
}
class UserType
{
    public function avatar($root, array $args)
    {
        // Here the $root could be your User model (or whatever you assigned to the User type)
        return $root->avatar($args['size']);
    }
}

@chrissm79 Thanks for the feedback.
Hmm it's a little awkward having to add a scope for filtering by id. But I guess I can make a custom directive instead?

The @field(resolver: "App\\Http\\GraphQL\\UserType@avatar") can do the job also.

You just tell where to resolve and take the input from $this->args on resolve function.

Check examples here:
https://github.com/nuwave

Right now my query looks like this

agreements(id: ID): [Agreement!]! @paginate(model: "Agreement", type: "connection", scopes: ["byAuth"])

With the @field I have to do a lot of the work manually, so was thinking about adding a ArgDirective like this

agreements(id: ID @filterBy(field: "id")): [Agreement!]! @paginate(model: "Agreement", type: "connection", scopes: ["byAuth"])

Just trying to find out how to access the query from a ArgMiddleware.
As I kinda like the syntax I made, as it doesn't bloat my scopes when just filtering by id.

What client you using?

I guess the magic its a bit limited, you trying to have all done from the schema,
Question for client its to know how should you response be made.,

here its a pagination example on root fields. #70
If you dont need pagination just build your own query resolve function, you can even use a repository function,

Setting it up with relay pagination.

Yes I am trying to do as much from schema as possible, just trying to figure out how to do it the best possible way with lighthouse.

But I guess the most clean way is to do it with scopes then, thanks!

@olivernybroe I LOVE the idea of argument filters! I have to figure out how to get it to work with @paginate, @belongsTo and @hasMany (maybe passing them through the $args variable), but I could see all kinds of possibilities. I'll re-open and label this as v2.x

@chrissm79 oh nice!

I don't know much about the internal system, but would it be possible to just pass the querybuilder? If we have access to the query we can pretty much do everything and then we would be able to make the argument filter directive relatively easy.

Also if we need some kind of way to specify filtering type eg. LIKE, EQ, NEQ and so on.

@olivernybroe, I'm probably going to have to pass some sort of parser instance (that you can grab the queryBuilder off of) through the $arg array. This way I can do things like between, or, and (which would require some sort of key). But if you're using these new filters on the @paginate, @belongsTo or @hasMany relationships then you don't even need to worry about the query builder as Lighthouse will handle that behind the scenes. So you could do something like:

type Query {
  posts(
    active: Boolean @eq
    authors: [Int] @in(key: "user_id")
    start: DateTime @between(key: "created_at")
    end: DateTime @between(key: "created_at")
  ): [Posts!]! @paginate(type: "paginator") # This could also be a `@hasMany` or `@belongsTo` on another type
}

And it would just work!

Wow, +1

Well done!
Smart solution with making a directive for each type, also solves the issue for coming with a good name.

Could we do so @in can be used with global ID?

Posts(author: [ID] @in(type: "User", key: "id") 

And then we can automatically lookup the user type id field and check if it has @globalId on it and then decide it.

@olivernybroe The last beta release has some argument filters to use!! The best way to see the possibilities (currently) is to check out the tests.

I just noticed your question about globalId fields. I think I can allow the @globalId directive to be added to an argument which will decode it before the query runs and would look like this:

type Query {
  # filter users with an `id` in the provided array and 
  # decodes the global id(s)
  users(include: [ID] @globalId @in(key: "id")): [User!]!
    @paginate(model: "App\\\Models\\\User")
}

and you'd query it like so:

{
  users(include: ["global-id-1", "global-id-2"] count: 15) {
    # only returns users with the ids provided (which will be decoded)
    name
    email
  }
}

I'll follow up if I get global id's to work this way.

@chrissm79 you awesome,
but, maybe, last commit can have an error? when you try to list routes.

Schema must contain unique named types but contains multiple types named Node
(see http://webonyx.github.io/graphql-php/type-system/#type-registry)

UPDATE:
having a second router caused this error, having middleware no need to have multiple endpoints, but,,,

Hey @chrissm79

The filters look great (I've only read through the tests)!

I see we could do @where(operator: "like") if we want a where like, but I feel that would be a common enough request that we should just add a @whereLike or simply @contains directive also and make it smart enough to check if the input value has any %, if it doesn't then wrap the entire thing in %'s?

@kikoseijo I'll check that out. The tests passed but I haven't pulled it into my project yet. However, it sounds like there is a type in the schema that's been declared twice (that or for some reason the scheme file is being generated more than once). I'll report back if I see something on my end when I update my dependencies to use the new beta.

@hailwood I get the point with the @whereLike. Maybe I could add an enum that could be WILDCARD, START or END to take care of the % placement.

@chrissm79 so what would that look like?

@hailwood Actually, I might need to use strings vs an enum (I might get an error since the enum isn't included in the user's schema). But it would look like the following:

type User {
  name: String!
}

type Query {
  # SELECT * FROM `users` WHERE `name` LIKE "{$name}"
  users(name: String @whereLike): [User!]!

  # SELECT * FROM `users` WHERE `name` LIKE "%{$name}"
  users(name: String @whereLike(filter: "starts_with")): [User!]!

  # SELECT * FROM `users` WHERE `name` LIKE "{$name}%"
  users(name: String @whereLike(filter: "ends_with")): [User!]!

  # SELECT * FROM `users` WHERE `name` LIKE "%{$name}%"
  users(name: String @whereLike(filter: "wildcard")): [User!]!
}

@chrissm79 forget it, i had a second router defined, from when i was playing with auth.
All fine. thanks anyway,

@chrissm79 interesting, although by defining it like that we actually like control away from the client right? Like a client couldn't then on your third example say "actually, I want a starts_with fillter"?

@chrissm79 Makes sense to let us define a default like operator. However I think you should give us more control, so we eg. Can say whereLike(starts_with: "___" ends_with: "%") it gives more control and doesn't hide what's happening as much.

@hailwood client still has full control. His example just gives us an default Wildcard to set. You can for example make the starts_with filter to a Wildcard filter by writing my search text%.

@chrissm79 / @olivernybroe did we get anywhere with defining @globalId on a filter?
I can't seem to get this to work? (no errors, just not decoded)

@hailwood, I haven't tested it, but it might work if you add the @globalId directive before the filter directive.

@olivernybroe unfortunately that doesn't seem to work either, I tried it in both positions.

Argument filters are available as of v2.1, you can check the docs https://lighthouse-php.netlify.com/docs/directives-queries.html

Was this page helpful?
0 / 5 - 0 ratings

Related issues

spawnia picture spawnia  路  4Comments

nguyentrongbang picture nguyentrongbang  路  3Comments

eriktisme picture eriktisme  路  4Comments

souljacker picture souljacker  路  3Comments

mikeerickson picture mikeerickson  路  3Comments