Skip to content

Commit

Permalink
feat(graphql/@sortBy)!: Default nulls ordering (#111, #110).
Browse files Browse the repository at this point in the history
  • Loading branch information
LastDragon-ru authored Jan 8, 2024
2 parents 795f9bf + 72f940b commit e528aec
Show file tree
Hide file tree
Showing 66 changed files with 1,644 additions and 325 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@
"laravel/scout": "^9.8.0|^10.0.0",
"mockery/mockery": "^1.6.2",
"nikic/php-parser": "^4.15",
"larastan/larastan": "2.7.0",
"larastan/larastan": "2.8.0",
"orchestra/testbench": "^8.0.0",
"phpstan/phpstan": "1.10.50",
"phpstan/phpstan": "1.10.54",
"phpstan/phpstan-mockery": "^1.0.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.1.0",
"phpunit/phpunit": "^10.1.0",
"slevomat/coding-standard": "^8.12.0",
"spaze/phpstan-disallowed-calls": "^2.16",
"spaze/phpstan-disallowed-calls": "^3.0.0",
"squizlabs/php_codesniffer": "^3.7.1",
"symfony/var-dumper": "^6.3.0",
"symplify/monorepo-builder": "^11.0.0"
Expand Down
7 changes: 3 additions & 4 deletions packages/eloquent/src/ModelHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,10 @@ static function (): string {
*/
class ModelHelperTest__Model extends Model {
/**
* @noinspection PhpMissingReturnTypeInspection
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint
* @phpstan-ignore-next-line Required for test
* @noinspection PhpMissingReturnTypeInspection
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint
*/
public function noTypeHint() {
public function noTypeHint() /* @phpstan-ignore-line Required for test */ {
return $this->belongsTo(self::class);
}

Expand Down
16 changes: 16 additions & 0 deletions packages/graphql/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,26 @@ Please also see [changelog](https://github.com/LastDragon-ru/lara-asp/releases)

# Upgrade from v5

## General

[include:file]: ../../docs/Shared/Upgrade/FromV5.md
[//]: # (start: fd146cf51ef5a8d9d13e0317c09860f472c63cb3d60d02f4d95deb3e12cae73d)
[//]: # (warning: Generated automatically. Do not edit.)

* [ ] Laravel v9 is not supported anymore. Migrate to the newer version.

[//]: # (end: fd146cf51ef5a8d9d13e0317c09860f472c63cb3d60d02f4d95deb3e12cae73d)

## `@searchBy`

* [ ] `enum SearchByTypeFlag { yes }` => `enum SearchByTypeFlag { Yes }`. 🤝

## `@sortBy`

* [ ] `enum SortByTypeFlag { yes }` => `enum SortByTypeFlag { Yes }`. 🤝

* [ ] `enum SortByTypeDirection { asc, desc }` => `enum SortByTypeDirection { Asc, Desc }`. 🤝

* [ ] `LastDragon_ru\LaraASP\GraphQL\SortBy\Builders\*` => `LastDragon_ru\LaraASP\GraphQL\SortBy\Sorters\*`.

* [ ] Update `LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeDefinition` implementations.
18 changes: 18 additions & 0 deletions packages/graphql/defaults/config.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php declare(strict_types = 1);

use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Direction;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Nulls;

/**
* -----------------------------------------------------------------------------
Expand All @@ -18,6 +20,7 @@
* },
* sort_by: array{
* operators: array<string, list<string|class-string<Operator>>>,
* nulls: Nulls|non-empty-array<value-of<Direction>, Nulls>|null,
* },
* stream: array{
* search: array{
Expand Down Expand Up @@ -78,6 +81,21 @@
'operators' => [
// empty
],

/**
* NULLs
*
* ---------------------------------------------------------------------
*
* Determines how the `NULL` values should be treatment. By default,
* there is no any processing, so the order of `NULL` depends on the
* database. It may be set for all (if single value) or for each
* direction (if array). Not all databases/builders may be supported.
* Please check the documentation for more details.
*
* @see Nulls
*/
'nulls' => null,
],

/**
Expand Down
54 changes: 54 additions & 0 deletions packages/graphql/docs/Directives/@sortBy.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,57 @@ query {
])
}
```

### NULLs ordering

`NULL`s order different in different databases. Sometimes you may to change it. There is no default/built-it support in Laravel nor Lighthouse, but you can do it! :) Please note, not all databases have native `NULLS FIRST`/`NULLS LAST` support (eg MySQL and SQL Server doesn't). The additional `ORDER BY` clause with `CASE WHEN` will be used for these databases. It may be slow for big datasets.

Default ordering can be changed via config. You may set it for all directions if single value used, in this case NULL always be first/last:

```php
<?php declare(strict_types = 1);

use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Nulls;

/**
* @var array{
* sort_by: array{
* nulls: Nulls|non-empty-array<value-of<Direction>, Nulls>|null,
* },
* } $settings
*/
$settings = [
'sort_by' => [
'nulls' => Nulls::First,
],
];

return $settings;
```

Or individually for each direction:

```php
<?php declare(strict_types = 1);

use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Direction;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Enums\Nulls;

/**
* @var array{
* sort_by: array{
* nulls: Nulls|non-empty-array<value-of<Direction>, Nulls>|null,
* },
* } $settings
*/
$settings = [
'sort_by' => [
'nulls' => [
Direction::Asc->value => Nulls::First,
Direction::Desc->value => Nulls::Last,
],
],
];

return $settings;
```
5 changes: 2 additions & 3 deletions packages/graphql/src/Builder/Contracts/TypeDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Type\Definition\NamedType;
use GraphQL\Type\Definition\Type;
use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfo;
use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator;

interface TypeDefinition {
/**
* Returns the type name for given Builder and Source.
*/
public function getTypeName(Manipulator $manipulator, BuilderInfo $builder, TypeSource $source): string;
public function getTypeName(Manipulator $manipulator, TypeSource $source): string;

/**
* Returns the type definition for given Source if possible. The name must be equal to `$name`.
Expand All @@ -22,7 +21,7 @@ public function getTypeName(Manipulator $manipulator, BuilderInfo $builder, Type
*/
public function getTypeDefinition(
Manipulator $manipulator,
string $name,
TypeSource $source,
string $name,
): TypeDefinitionNode|Type|null;
}
17 changes: 17 additions & 0 deletions packages/graphql/src/Builder/Enums/Flag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\Builder\Enums;

use GraphQL\Type\Definition\Deprecated;
use GraphQL\Type\Definition\Description;

enum Flag {
case Yes;

/**
* @deprecated 5.4.0 Please use {@link Flag::Yes} instead.
*/
#[Deprecated('Please use `Yes` instead.')]
#[Description('')]
case yes;
}
4 changes: 2 additions & 2 deletions packages/graphql/src/Builder/Manipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function getType(string $definition, TypeSource $source): string {
}

// Exists?
$name = $instance->getTypeName($this, $this->getBuilderInfo(), $source);
$name = $instance->getTypeName($this, $source);

if ($this->isTypeDefinitionExists($name)) {
return $name;
Expand All @@ -98,7 +98,7 @@ public function getType(string $definition, TypeSource $source): string {
$this->addFakeTypeDefinition($name);

// Create new
$node = $instance->getTypeDefinition($this, $name, $source);
$node = $instance->getTypeDefinition($this, $source, $name);

if (!$node) {
throw new TypeDefinitionImpossibleToCreateType($definition, $source);
Expand Down
72 changes: 72 additions & 0 deletions packages/graphql/src/Builder/Traits/BuilderHelperFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\Builder\Traits;

use Illuminate\Container\Container;

use function array_key_exists;
use function is_a;
use function is_object;

/**
* @internal
*/
trait BuilderHelperFactory {
/**
* @var array<class-string, class-string>
*/
private array $helpers = [];

/**
* @var array<class-string, class-string>
*/
private array $classes = [];

/**
* @var array<class-string, ?object>
*/
private array $instances = [];

/**
* @param class-string $builder
* @param class-string $helper
*/
private function addHelper(string $builder, string $helper): static {
$this->helpers[$builder] = $helper;

return $this;
}

private function getHelper(object $builder): ?object {
if (!array_key_exists($builder::class, $this->instances)) {
$class = $this->getHelperClass($builder::class);
$this->instances[$builder::class] = $class
? Container::getInstance()->make($class)
: null;
}

return $this->instances[$builder::class];
}

/**
* @param object|class-string $builder
*
* @return ?class-string
*/
private function getHelperClass(object|string $builder): ?string {
if (is_object($builder)) {
$builder = $builder::class;
}

if (!isset($this->classes[$builder])) {
foreach ($this->helpers as $class => $sorter) {
if (is_a($builder, $class, true)) {
$this->classes[$builder] = $sorter;
break;
}
}
}

return $this->classes[$builder];
}
}
2 changes: 1 addition & 1 deletion packages/graphql/src/Builder/Types/InputObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ abstract protected function getDescription(
#[Override]
public function getTypeDefinition(
Manipulator $manipulator,
string $name,
TypeSource $source,
string $name,
): TypeDefinitionNode|Type|null {
// Source?
if (
Expand Down
37 changes: 17 additions & 20 deletions packages/graphql/src/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;
use LastDragon_ru\LaraASP\Core\Provider\WithConfig;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scout\FieldResolver as ScoutFieldResolver;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scout\FieldResolver as ScoutFieldResolverContract;
use LastDragon_ru\LaraASP\GraphQL\Builder\Manipulator;
use LastDragon_ru\LaraASP\GraphQL\Builder\Scout\DefaultFieldResolver as ScoutDefaultFieldResolver;
use LastDragon_ru\LaraASP\GraphQL\Builder\Scout\DefaultFieldResolver as ScoutFieldResolver;
use LastDragon_ru\LaraASP\GraphQL\Printer\DirectiveResolver;
use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByDirective;
use LastDragon_ru\LaraASP\GraphQL\SearchBy\Operators as SearchByOperators;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Contracts\SorterFactory as SorterFactoryContract;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByDirective;
use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators as SortByOperators;
use LastDragon_ru\LaraASP\GraphQL\SortBy\SorterFactory;
use LastDragon_ru\LaraASP\GraphQL\Stream\Contracts\StreamFactory as StreamFactoryContract;
use LastDragon_ru\LaraASP\GraphQL\Stream\Definitions\StreamDirective;
use LastDragon_ru\LaraASP\GraphQL\Stream\StreamFactory;
use LastDragon_ru\LaraASP\GraphQL\Utils\Definitions\LaraAspAsEnumDirective;
use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\DirectiveResolver as DirectiveResolverContract;
use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\Printer as SchemaPrinterContract;
use LastDragon_ru\LaraASP\GraphQLPrinter\Contracts\Settings as SettingsContract;
Expand All @@ -43,37 +46,31 @@ public function register(): void {
parent::register();

$this->registerBindings();
$this->registerDirectives();
$this->registerOperators();
$this->registerSchemaPrinter();
}

protected function bootDirectives(Dispatcher $dispatcher): void {
$dispatcher->listen(
RegisterDirectiveNamespaces::class,
static function (): string {
return implode('\\', array_slice(explode('\\', SearchByDirective::class), 0, -1));
},
);
$dispatcher->listen(
RegisterDirectiveNamespaces::class,
static function (): string {
return implode('\\', array_slice(explode('\\', SortByDirective::class), 0, -1));
},
);
$dispatcher->listen(
RegisterDirectiveNamespaces::class,
static function (): string {
return implode('\\', array_slice(explode('\\', StreamDirective::class), 0, -1));
static function (): array {
return [
implode('\\', array_slice(explode('\\', SearchByDirective::class), 0, -1)),
implode('\\', array_slice(explode('\\', SortByDirective::class), 0, -1)),
implode('\\', array_slice(explode('\\', StreamDirective::class), 0, -1)),
implode('\\', array_slice(explode('\\', LaraAspAsEnumDirective::class), 0, -1)),
];
},
);
}

protected function registerBindings(): void {
$this->app->bindIf(StreamFactoryContract::class, StreamFactory::class);
$this->app->scopedIf(SorterFactoryContract::class, SorterFactory::class);
$this->app->scopedIf(StreamFactoryContract::class, StreamFactory::class);
$this->app->scopedIf(ScoutFieldResolverContract::class, ScoutFieldResolver::class);
}

protected function registerDirectives(): void {
$this->app->bindIf(ScoutFieldResolver::class, ScoutDefaultFieldResolver::class);
protected function registerOperators(): void {
$this->callAfterResolving(
Manipulator::class,
static function (Manipulator $manipulator): void {
Expand Down
Loading

0 comments on commit e528aec

Please sign in to comment.