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

Flexible client side querying through @whereConstraints directive #753

Merged
merged 15 commits into from
May 12, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add @whereConstraints directive as an experimental, optional feature
  • Loading branch information
spawnia committed Apr 25, 2019
commit dcae6f7bf4c8f542c31f20da3101122e190a8df6
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"name": "Christopher Moore",
"email": "chris@nuwavecommerce.com",
"homepage": "https://www.nuwavecommerce.com"
},
{
"name": "Benedikt Franke",
"email": "benedikt@franke.tech",
"homepage": "https://franke.tech"
}
],
"support": {
Expand All @@ -35,12 +40,15 @@
},
"require-dev": {
"laravel/scout": "^4.0",
"mll-lab/graphql-php-scalars": "^2.1",
"mockery/mockery": "^1.0",
"orchestra/database": "3.5.*|3.6.*|3.7.*|3.8.x-dev",
"orchestra/testbench": "3.5.*|3.6.*|3.7.*|3.8.*",
"pusher/pusher-php-server": "^3.2"
},
"suggest": {
"laravel/scout": "Required for the @search directive",
"mll-lab/graphql-php-scalars": "Useful scalar types, required for @whereConstraints",
"mll-lab/laravel-graphql-playground": "GraphQL IDE for better development workflow - integrated with Laravel"
},
"autoload": {
Expand Down
75 changes: 75 additions & 0 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,81 @@ input DateRange {
If the name of the argument does not match the database column,
pass the actual column name as the `key`.

## @whereConstraints

Add a dynamically client-controlled where constraint to a fields query.

### Setup

**This is an experimental feature and not included in Lighthouse by default.**

First, enable the service provider:

```php
'providers' => [
\Nuwave\Lighthouse\Defer\DeferServiceProvider::class,
],
```

It depends upon [mll-lab/graphql-php-scalars](https://github.com/mll-lab/graphql-php-scalars):

composer require mll-lab/graphql-php-scalars

### Usage

The argument it is defined on may have any name but **must** be
of the input type `WhereConstraints`.

```graphql
type Query {
people(where: WhereConstraints @whereConstraints): [Person!]!
}
```

This is how you can use it to construct a complex query
that gets actors over age 37 who either have red hair or are at least 150cm.

```graphql
{
people(
filter: {
where: [
{
AND: [
{ column: "age", operator: ">" value: 37 }
{ column: "type", value: "Actor" }
{
OR: [
{ column: "haircolour", value: "red" }
{ column: "height", operator: ">=", value: 150 }
]
}
]
}
]
}
) {
name
}
}
```

The definition for the `WhereConstraints` input is automatically included
within your schema.

```graphql
input WhereConstraints {
column: String
operator: String
value: Mixed
AND: [WhereConstraints!]
OR: [WhereConstraints!]
NOT: [WhereConstraints!]
}

scalar Mixed @scalar(class: "MLL\\GraphQLScalars\\Mixed")
```

## @whereNotBetween

Verify that a column's value lies outside of two values.
Expand Down
6 changes: 3 additions & 3 deletions src/Schema/AST/ASTBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ interface Node @interface(resolveType: "Nuwave\\\Lighthouse\\\Schema\\\NodeRegis
)
)
->addFieldToQueryType(
PartialParser::fieldDefinition(
'node(id: ID! @globalId): Node @field(resolver: "Nuwave\\\Lighthouse\\\Schema\\\NodeRegistry@resolve")'
)
PartialParser::fieldDefinition('
node(id: ID! @globalId): Node @field(resolver: "Nuwave\\\Lighthouse\\\Schema\\\NodeRegistry@resolve")
')
);
}

Expand Down
82 changes: 82 additions & 0 deletions src/WhereConstraints/WhereConstraintsDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Nuwave\Lighthouse\WhereConstraints;

use GraphQL\Error\Error;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective;

class WhereConstraintsDirective extends BaseDirective implements ArgBuilderDirective
{
const NAME = 'whereConstraints';

/**
* Name of the directive.
*
* @return string
*/
public function name(): string
{
return self::NAME;
}

/**
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
* @param mixed $whereConstraints
* @param bool $nestedOr
* @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
*/
public function handleBuilder($builder, $whereConstraints, bool $nestedOr = false)
{
if ($andConnectedConstraints = $whereConstraints['AND'] ?? null) {
$builder->whereNested(
function ($builder) use ($andConnectedConstraints): void {
foreach ($andConnectedConstraints as $constraint) {
$this->handleBuilder($builder, $constraint);
}
}
);
}

if ($orConnectedConstraints = $whereConstraints['OR'] ?? null) {
$builder->whereNested(
function ($builder) use ($orConnectedConstraints): void {
foreach ($orConnectedConstraints as $constraint) {
$this->handleBuilder($builder, $constraint, true);
}
}
);
}

if ($notConnectedConstraints = $whereConstraints['NOT'] ?? null) {
$builder->whereNested(
function ($builder) use ($notConnectedConstraints): void {
foreach ($notConnectedConstraints as $constraint) {
$this->handleBuilder($builder, $constraint);
}
},
'not'
);
}

if ($column = $whereConstraints['column'] ?? null) {
if (! $value = $whereConstraints['value']) {
throw new Error(
"Did not receive a value to match the WhereConstraints for column {$column}."
);
}

$where = $nestedOr
? 'orWhere'
: 'where';

$builder->{$where}(
$column,
$whereConstraints['operator'],
$value
);
}

return $builder;
}
}
51 changes: 51 additions & 0 deletions src/WhereConstraints/WhereConstraintsServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Nuwave\Lighthouse\WhereConstraints;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Events\Dispatcher;
use Nuwave\Lighthouse\Events\ManipulateAST;
use Nuwave\Lighthouse\Schema\AST\PartialParser;
use Nuwave\Lighthouse\Schema\Factories\DirectiveFactory;

class WhereConstraintsServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @param \Nuwave\Lighthouse\Schema\Factories\DirectiveFactory $directiveFactory
* @param \Illuminate\Contracts\Events\Dispatcher $dispatcher
* @return void
*/
public function boot(DirectiveFactory $directiveFactory, Dispatcher $dispatcher): void
{
$directiveFactory->addResolved(
WhereConstraintsDirective::NAME,
WhereConstraintsDirective::class
);

$dispatcher->listen(
ManipulateAST::class,
function(ManipulateAST $manipulateAST): void {
$manipulateAST->documentAST
->setDefinition(
PartialParser::inputObjectTypeDefinition('
input WhereConstraints {
column: String
operator: String
value: Mixed
AND: [WhereConstraints!]
OR: [WhereConstraints!]
NOT: [WhereConstraints!]
}
')
)
->setDefinition(
PartialParser::scalarTypeDefinition('
scalar Mixed @scalar(class: "MLL\\GraphQLScalars\\Mixed")
')
);
}
);
}
}
Loading