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

Implement support for querying soft deleted elements #937

Merged
merged 25 commits into from
Sep 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
36b0ba5
implement support for querying soft deleted elements
lorado Aug 27, 2019
f549bd0
fix styleci
lorado Aug 27, 2019
98a7030
fix retrieving model from scout builder
lorado Aug 27, 2019
6ecdab6
fix style
lorado Aug 27, 2019
4833406
Merge branch 'master' into support_soft_delete_queries
lorado Aug 29, 2019
e9c8543
Merge remote-tracking branch 'master/master' into support_soft_delete…
lorado Aug 29, 2019
903043e
Merge remote-tracking branch 'origin/support_soft_delete_queries' int…
lorado Aug 29, 2019
c2eab23
Rename chapter to match laravel docs chapter
lorado Aug 29, 2019
4d81f76
refactor: implement @softDeletes and @trash directives
lorado Aug 30, 2019
21fe788
update tests
lorado Aug 30, 2019
f7e22c5
update changelog
lorado Aug 30, 2019
7d3a6f3
update readme
lorado Aug 30, 2019
9b47fa2
fix code style
lorado Aug 30, 2019
a3d2bac
fix code style
lorado Aug 30, 2019
eeb2276
add API docs for new directives
lorado Aug 30, 2019
ceb90ff
Merge remote-tracking branch 'master/master' into support_soft_delete…
lorado Aug 30, 2019
af52846
Merge from master, set directives definitions, update docs
lorado Aug 30, 2019
5a1eea6
Merge remote-tracking branch 'master/master' into support_soft_delete…
lorado Sep 2, 2019
e125b29
Move docs to seperate page
spawnia Sep 2, 2019
744c214
Update definition docs
spawnia Sep 2, 2019
ac65250
Throw if model does not use soft deletes
spawnia Sep 2, 2019
9a5aa9c
codestyle
spawnia Sep 2, 2019
6df4243
trash -> trashed
spawnia Sep 2, 2019
dcd726d
Merge remote-tracking branch 'master/master' into support_soft_delete…
lorado Sep 2, 2019
93410a3
changelog
spawnia Sep 2, 2019
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/nuwave/lighthouse/compare/v4.2.1...master)

### Added

- Add `@softDeletes` and `@trashed` directives to enable
filtering soft deleted models https://github.com/nuwave/lighthouse/pull/937

## [4.2.1](https://github.com/nuwave/lighthouse/compare/v4.2.0...v4.2.1)

### Fixed
Expand Down
49 changes: 49 additions & 0 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -2100,6 +2100,35 @@ query myQuery($someTest: Boolean) {
}
```

## @softDeletes

```graphql
"""
Allows to filter if trashed elements should be fetched.
This manipulates the schema by adding the argument
`trashed: Trashed @trashed` to the field.
"""
directive @softDeletes on FIELD_DEFINITION
```

The following schema definition from a `.graphql` file:

```graphql
type Query {
tasks: [Tasks!]! @all @softDeletes
}
```

Will result in a schema that looks like this:

```graphql
type Query {
tasks(trashed: Trashed @trashed): [Tasks!]! @all
}
```

Find out how the added filter works: [`@trashed`](#trashed)

## @spread

Spread out the nested values of an argument of type input object into it's parent.
Expand Down Expand Up @@ -2187,6 +2216,26 @@ directive @subscription(
) on FIELD_DEFINITION
```

## @trashed

```graphql
"""
Allows to filter if trashed elements should be fetched.
"""
directive @trashed on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
```

The most convenient way to use this directive is through [`@softDeletes`](#softdeletes).

If you want to add it manually, make sure the argument is of the
enum type `Trashed`:

```graphql
type Query {
flights(trashed: Trashed @trashed): [Flight!]! @all
}
```

## @trim

Run the `trim` function on an input value.
Expand Down
2 changes: 1 addition & 1 deletion docs/master/eloquent/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ And can be queried like this:
}
```

## Adding query constraints
## Adding Query Constraints

Lighthouse provides built-in directives to enhance your queries by giving
additional query capabilities to the client.
Expand Down
44 changes: 44 additions & 0 deletions docs/master/eloquent/soft-deleting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Soft Deleting

Lighthouse offers convenient helpers to work with models that utilize
[soft deletes](https://laravel.com/docs/eloquent#soft-deleting).

## Filter Soft Deleted Models

If your model uses the `Illuminate\Database\Eloquent\SoftDeletes` trait,
you can add the [`@softDeletes`](../api-reference/directives.md) directive to a field
to be able to query `onlyTrashed`, `withTrashed` or `withoutTrashed` elements.

```graphql
type Query {
flights: [Flight!]! @all @softDeletes
}
```

Lighthouse will add an argument `trashed` to the field definition
and automatically include the enum `Trashed`.

```graphql
type Query {
flights(trashed: Trashed @trashed): [Flight!]! @all
}

"""
Used for filtering
"""
enum Trashed {
ONLY @enum(value: "only")
WITH @enum(value: "with")
WITHOUT @enum(value: "without")
}
```

You can include soft deleted models in your result with a query like this:

```graphql
{
flights(trashed: WITH) {
id
}
}
```
1 change: 1 addition & 0 deletions docs/master/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = [
['eloquent/getting-started', 'Getting Started'],
'eloquent/relationships',
'eloquent/polymorphic-relationships',
'eloquent/soft-deleting',
'eloquent/nested-mutations',
]
},
Expand Down
21 changes: 21 additions & 0 deletions src/Schema/AST/ASTBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public function build(): DocumentAST
$this->addPaginationInfoTypes();
$this->addNodeSupport();
$this->addOrderByTypes();
$this->addTrashedEnum();

// Listeners may manipulate the DocumentAST that is passed by reference
// into the ManipulateAST event. This can be useful for extensions
Expand Down Expand Up @@ -361,4 +362,24 @@ enum SortOrder {
')
);
}

/**
* Add Trashed enum to filter soft deleted models.
*
* @see \Nuwave\Lighthouse\Schema\Directives\TrashedDirective
*
* @return void
*/
protected function addTrashedEnum(): void
{
$this->documentAST->setTypeDefinition(
PartialParser::enumTypeDefinition('
enum Trashed {
ONLY @enum(value: "only")
WITH @enum(value: "with")
WITHOUT @enum(value: "without")
}
')
);
}
}
54 changes: 54 additions & 0 deletions src/Schema/Directives/SoftDeletesDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Nuwave\Lighthouse\Schema\Directives;

use Nuwave\Lighthouse\Schema\AST\ASTHelper;
use GraphQL\Language\AST\FieldDefinitionNode;
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
use Nuwave\Lighthouse\Schema\AST\PartialParser;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
use Nuwave\Lighthouse\Support\Contracts\FieldManipulator;

class SoftDeletesDirective extends BaseDirective implements FieldManipulator, DefinedDirective
{
/**
* Name of the directive.
*
* @return string
*/
public function name(): string
{
return 'softDeletes';
}

public static function definition(): string
{
return /* @lang GraphQL */ <<<'SDL'
"""
Allows to filter if trashed elements should be fetched.
This manipulates the schema by adding the argument
`trashed: Trashed @trashed` to the field.
"""
directive @softDeletes on FIELD_DEFINITION
SDL;
}

/**
* @param \Nuwave\Lighthouse\Schema\AST\DocumentAST $documentAST
* @param \GraphQL\Language\AST\FieldDefinitionNode $fieldDefinition
* @param \GraphQL\Language\AST\ObjectTypeDefinitionNode $parentType
* @return void
*/
public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode &$parentType): void
{
$softDeletesArgument = PartialParser::inputValueDefinition(/* @lang GraphQL */ <<<'SDL'
"""
Allows to filter if trashed elements should be fetched.
"""
trashed: Trashed @trashed
SDL
);
$fieldDefinition->arguments = ASTHelper::mergeNodeList($fieldDefinition->arguments, [$softDeletesArgument]);
}
}
69 changes: 69 additions & 0 deletions src/Schema/Directives/TrashedDirective.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Nuwave\Lighthouse\Schema\Directives;

use Laravel\Scout\Builder as ScoutBuilder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\Relation;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective;

class TrashedDirective extends BaseDirective implements ArgBuilderDirective, DefinedDirective
{
const MODEL_MUST_USE_SOFT_DELETES = 'Use @trashed only for Model classes that use the SoftDeletes trait.';

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

public static function definition(): string
{
return /* @lang GraphQL */ <<<'SDL'
"""
Allows to filter if trashed elements should be fetched.
"""
directive @trashed on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
SDL;
}

/**
* Apply withTrashed, onlyTrashed or withoutTrashed to given $builder if needed.
*
* @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder
* @param string|null $value "with", "without" or "only"
*
* @return \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder
*/
public function handleBuilder($builder, $value)
{
if ($builder instanceof Relation) {
$model = $builder->getRelated();
} elseif ($builder instanceof ScoutBuilder) {
$model = $builder->model;
} else {
$model = $builder->getModel();
}

if (! in_array(SoftDeletes::class, class_uses_recursive($model))) {
throw new DefinitionException(
self::MODEL_MUST_USE_SOFT_DELETES
);
}

if (! isset($value)) {
return $builder;
}

$trashedModificationMethod = "{$value}Trashed";
$builder->{$trashedModificationMethod}();

return $builder;
}
}
Loading