Skip to content

Commit

Permalink
feat(graphql): @sortBy nulls ordering operators nullsFirst/`nulls…
Browse files Browse the repository at this point in the history
…Last` (#119)
  • Loading branch information
LastDragon-ru authored Jan 11, 2024
2 parents 79b3bf0 + b47f7df commit e260593
Show file tree
Hide file tree
Showing 23 changed files with 892 additions and 46 deletions.
21 changes: 18 additions & 3 deletions packages/graphql/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases)

* [ ] If you are testing generated queries, you need to update `sort_by_*` alias to `lara_asp_graphql__sort_by__*`.

* [ ] If you are overriding Extra operators, you may need to add `SortByOperators::Extra` to use new built-in:

```php
$settings = [
'sort_by' => [
'operators' => [
SortByOperators::Extra => [
SortByOperators::Extra,
SortByOperatorRandomDirective::class,
],
],
],
];
```

## API

This section is actual only if you are extending the package. Please review and update (listed the most significant changes only):
Expand All @@ -73,6 +88,6 @@ This section is actual only if you are extending the package. Please review and

* [ ] To get `BuilderInfo` instance within Operator the `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context` should be used instead of `LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator`:

```php
$context->get(\LastDragon_ru\LaraASP\GraphQL\Builder\Contexts\AstManipulation\AstManipulation::class)?->builderInfo
```
```php
$context->get(\LastDragon_ru\LaraASP\GraphQL\Builder\Contexts\AstManipulation\AstManipulation::class)?->builderInfo
```
12 changes: 12 additions & 0 deletions packages/graphql/docs/Directives/@sortBy.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,15 @@ $settings = [

return $settings;
```

The query is also supported and have highest priority (will override default settings):

```graphql
query {
# ORDER BY user.name ASC NULLS FIRST, text DESC
comments(order: [
{nullsFirst: {user: {name: asc}}}
{text: desc}
])
}
```
8 changes: 6 additions & 2 deletions packages/graphql/src/Builder/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
class Context implements ContextContract {
/**
* @var array<class-string, object|null>
* @var array<class-string, object>
*/
private array $context = [];

Expand Down Expand Up @@ -38,7 +38,11 @@ public function override(array $context): static {
$overridden = clone $this;

foreach ($context as $key => $value) {
$overridden->context[$key] = $value;
if ($value !== null) {
$overridden->context[$key] = $value;
} else {
unset($overridden->context[$key]);
}
}

return $overridden;
Expand Down
36 changes: 36 additions & 0 deletions packages/graphql/src/Builder/ContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types = 1);

namespace Builder;

use LastDragon_ru\LaraASP\GraphQL\Builder\Context;
use LastDragon_ru\LaraASP\GraphQL\Testing\Package\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;

/**
* @internal
*/
#[CoversClass(Context::class)]
class ContextTest extends TestCase {
public function testContext(): void {
$context = new Context();
$class = (new class('origin') {
public function __construct(
public readonly string $value,
) {
// empty
}
})::class;

self::assertFalse($context->has($class));
self::assertNull($context->get($class));

$overridden = $context->override([$class => new $class('overridden')]);

self::assertNotSame($context, $overridden);
self::assertFalse($context->has($class));
self::assertNull($context->get($class));
self::assertTrue($overridden->has($class));
self::assertNotNull($overridden->get($class));
self::assertEquals('overridden', $overridden->get($class)->value);
}
}
29 changes: 4 additions & 25 deletions packages/graphql/src/Builder/Directives/PropertyDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeProvider;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionEmpty;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyOperators;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\HandlerInvalidConditions;
use LastDragon_ru\LaraASP\GraphQL\Builder\Property;
use LastDragon_ru\LaraASP\GraphQL\Utils\ArgumentFactory;
use LastDragon_ru\LaraASP\GraphQL\Builder\Traits\PropertyOperator;
use Nuwave\Lighthouse\Execution\Arguments\Argument;
use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet;
use Override;

use function count;

abstract class PropertyDirective extends OperatorDirective {
use PropertyOperator;

#[Override]
public static function getName(): string {
return 'property';
Expand All @@ -41,23 +37,6 @@ public function call(
Argument $argument,
Context $context,
): object {
if (!($argument->value instanceof ArgumentSet)) {
throw new HandlerInvalidConditions($handler);
}

// Empty?
if (count($argument->value->arguments) === 0) {
throw new ConditionEmpty();
}

// Valid?
if (count($argument->value->arguments) > 1) {
throw new ConditionTooManyOperators(
ArgumentFactory::getArgumentsNames($argument->value),
);
}

// Apply
return $handler->handle($builder, $property, $argument->value, $context);
return $this->handle($handler, $builder, $property, $argument, $context);
}
}
51 changes: 51 additions & 0 deletions packages/graphql/src/Builder/Traits/PropertyOperator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\Builder\Traits;

use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionEmpty;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\Client\ConditionTooManyOperators;
use LastDragon_ru\LaraASP\GraphQL\Builder\Exceptions\HandlerInvalidConditions;
use LastDragon_ru\LaraASP\GraphQL\Builder\Property;
use LastDragon_ru\LaraASP\GraphQL\Utils\ArgumentFactory;
use Nuwave\Lighthouse\Execution\Arguments\Argument;
use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet;

use function count;

trait PropertyOperator {
/**
* @template TBuilder of object
*
* @param TBuilder $builder
*
* @return TBuilder
*/
protected function handle(
Handler $handler,
object $builder,
Property $property,
Argument $argument,
Context $context,
): object {
if (!($argument->value instanceof ArgumentSet)) {
throw new HandlerInvalidConditions($handler);
}

// Empty?
if (count($argument->value->arguments) === 0) {
throw new ConditionEmpty();
}

// Valid?
if (count($argument->value->arguments) > 1) {
throw new ConditionTooManyOperators(
ArgumentFactory::getArgumentsNames($argument->value),
);
}

// Apply
return $handler->handle($builder, $property, $argument->value, $context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions;

use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators\Extra\NullsFirst;

class SortByOperatorNullsFirstDirective extends NullsFirst {
// Lighthouse loads all classes from directive namespace this leads to
// 'Class "Orchestra\Testbench\TestCase" not found' error for our *Test
// classes. This class required to avoid this error.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions;

use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators\Extra\NullsLast;

class SortByOperatorNullsLastDirective extends NullsLast {
// Lighthouse loads all classes from directive namespace this leads to
// 'Class "Orchestra\Testbench\TestCase" not found' error for our *Test
// classes. This class required to avoid this error.
}
61 changes: 61 additions & 0 deletions packages/graphql/src/SortBy/Directives/DirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ public function testManipulateArgDefinitionTypeRegistry(): void {
}

public function testManipulateArgDefinitionTypeRegistryEmpty(): void {
config([
Package::Name.'.sort_by.operators' => [
Operators::Extra => [
// empty
],
],
]);

$type = new ObjectType([
'name' => 'TestType',
'fields' => [
Expand Down Expand Up @@ -390,6 +398,7 @@ static function (): void {
config([
"{$package}.sort_by.operators" => [
Operators::Extra => [
Operators::Extra,
SortByOperatorRandomDirective::class,
],
],
Expand Down Expand Up @@ -544,6 +553,58 @@ static function (): void {
]);
},
],
'nullsFirst' => [
[
'query' => <<<'SQL'
select
*
from
"test_objects"
order by
"id" DESC NULLS FIRST,
"renamed" asc
SQL
,
'bindings' => [],
],
[
[
'nullsFirst' => [
'id' => 'desc',
],
],
[
'value' => 'asc',
],
],
null,
],
'nullsLast' => [
[
'query' => <<<'SQL'
select
*
from
"test_objects"
order by
"id" ASC NULLS LAST,
"renamed" desc
SQL
,
'bindings' => [],
],
[
[
'nullsLast' => [
'id' => 'Asc',
],
],
[
'value' => 'Desc',
],
],
null,
],
]),
))->getData();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ on
| INPUT_FIELD_DEFINITION
| SCALAR

directive @sortByOperatorNullsFirst
on
| ENUM
| INPUT_FIELD_DEFINITION
| SCALAR

directive @sortByOperatorNullsLast
on
| ENUM
| INPUT_FIELD_DEFINITION
| SCALAR

directive @sortByOperatorProperty
on
| ENUM
Expand Down Expand Up @@ -39,6 +51,18 @@ enum SortByTypeDirection {
Sort clause for `type Comment` (only one property allowed at a time).
"""
input SortByClauseComment {
"""
NULLs first
"""
nullsFirst: SortByClauseComment
@sortByOperatorNullsFirst

"""
NULLs last
"""
nullsLast: SortByClauseComment
@sortByOperatorNullsLast

"""
Property clause.
"""
Expand Down Expand Up @@ -67,6 +91,18 @@ input SortByClauseUser {
"""
name: SortByTypeDirection
@sortByOperatorField

"""
NULLs first
"""
nullsFirst: SortByClauseUser
@sortByOperatorNullsFirst

"""
NULLs last
"""
nullsLast: SortByClauseUser
@sortByOperatorNullsLast
}

"""
Expand All @@ -84,6 +120,18 @@ input SortByClauseUsersSort {
"""
name: SortByTypeDirection
@sortByOperatorField

"""
NULLs first
"""
nullsFirst: SortByClauseUsersSort
@sortByOperatorNullsFirst

"""
NULLs last
"""
nullsLast: SortByClauseUsersSort
@sortByOperatorNullsLast
}

type Comment {
Expand Down
Loading

0 comments on commit e260593

Please sign in to comment.