Skip to content

Commit

Permalink
feat(graphql/@stream)!: ResolveInfo::enhanceBuilder() will be use…
Browse files Browse the repository at this point in the history
…d only if really necessary (also, it is faster, and required to enhance/support any builder).
  • Loading branch information
LastDragon-ru committed Feb 15, 2024
1 parent a9dd684 commit 08f0e26
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 81 deletions.
2 changes: 2 additions & 0 deletions packages/graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ Represents [JSON](https://json.org) string.
[Scout](https://laravel.com/docs/scout) is also supported 🤩. You just need to add [`@search`](https://lighthouse-php.com/master/api-reference/directives.html#search) directive to an argument. Please note that available operators depend on [Scout itself](https://laravel.com/docs/scout#where-clauses).
Please note that if the [`@search`](https://lighthouse-php.com/master/api-reference/directives.html#search) directive added, the generated query will expect the Scout builder only. So recommended using non-nullable `String!` type to avoid using the Eloquent builder (it will happen if the search argument missed or `null`; see also [lighthouse#2465](https://github.com/nuwave/lighthouse/issues/2465).
# Input type auto-generation
The type used with the Builder directives like `@searchBy`/`@sortBy` may be Explicit (when you specify the `input` name `field(where: InputTypeName @searchBy): [Object!]!`) or Implicit (when the `_` used, `field(where: _ @searchBy): [Object!]!`). They are processing a bit differently.
Expand Down
4 changes: 4 additions & 0 deletions packages/graphql/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,7 @@ This section is actual only if you are extending the package. Please review and
* [ ] `@sortByOperatorProperty` => `@sortByOperatorChild` (and class too)

* [ ] `@sortByOperatorField` => `@sortByOperatorSort` (and class too)

* [ ] `LastDragon_ru\LaraASP\GraphQL\Stream\Contracts\StreamFactory::enhance()` removed

* [ ] `LastDragon_ru\LaraASP\GraphQL\Stream\Directives\Directive`
23 changes: 23 additions & 0 deletions packages/graphql/src/Builder/Contracts/Enhancer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\GraphQL\Builder\Contracts;

use LastDragon_ru\LaraASP\GraphQL\Builder\Field;
use Nuwave\Lighthouse\Execution\Arguments\Argument;
use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet;

interface Enhancer {
/**
* @template TBuilder of object
*
* @param TBuilder $builder
*
* @return TBuilder
*/
public function enhance(
object $builder,
ArgumentSet|Argument $value,
Field $field = null,
Context $context = null,
): object;
}
51 changes: 37 additions & 14 deletions packages/graphql/src/Builder/Directives/HandlerDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextBuilderInfo;
use LastDragon_ru\LaraASP\GraphQL\Builder\Context\HandlerContextImplicit;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Context as ContextContract;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Enhancer;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Handler;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scope;
Expand Down Expand Up @@ -49,7 +50,7 @@
* @see HandlerContextBuilderInfo
* @see HandlerContextImplicit
*/
abstract class HandlerDirective extends BaseDirective implements Handler {
abstract class HandlerDirective extends BaseDirective implements Handler, Enhancer {
use WithManipulator;
use WithSource;

Expand Down Expand Up @@ -95,22 +96,44 @@ public function handleScoutBuilder(ScoutBuilder $builder, mixed $value): ScoutBu
*
* @return T
*/
protected function handleAnyBuilder(object $builder, mixed $value, ContextContract $context = null): object {
protected function handleAnyBuilder(
object $builder,
mixed $value,
Field $field = null,
ContextContract $context = null,
): object {
if ($value !== null && $this->definitionNode instanceof InputValueDefinitionNode) {
$argument = !($value instanceof Argument)
$argument = !($value instanceof Argument)
? $this->getFactory()->getArgument($this->definitionNode, $value)
: $value;
$isList = $this->definitionNode->type instanceof ListTypeNode;
$conditions = $isList && is_array($argument->value)
? $argument->value
: [$argument->value];

foreach ($conditions as $condition) {
if ($condition instanceof ArgumentSet) {
$builder = $this->handle($builder, new Field(), $condition, $context ?? new Context());
} else {
throw new HandlerInvalidConditions($this);
}
$builder = $this->enhance($builder, $argument, $field, $context);
}

return $builder;
}

#[Override]
public function enhance(
object $builder,
ArgumentSet|Argument $value,
Field $field = null,
ContextContract $context = null,
): object {
$field ??= new Field();
$context ??= new Context();
$conditions = match (true) {
$value instanceof ArgumentSet => [$value],
!is_array($value->value) => [$value->value],
default => $value->value,
};

foreach ($conditions as $condition) {
if ($condition instanceof ArgumentSet) {
$builder = $this->handle($builder, $field, $condition, $context);
} elseif ($condition === null) {
// nothing to do, skip
} else {
throw new HandlerInvalidConditions($this);
}
}

Expand Down
16 changes: 0 additions & 16 deletions packages/graphql/src/Stream/Contracts/StreamFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
namespace LastDragon_ru\LaraASP\GraphQL\Stream\Contracts;

use LastDragon_ru\LaraASP\GraphQL\Stream\Offset;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

/**
* @template TBuilder of object
Expand All @@ -17,20 +15,6 @@ interface StreamFactory {
*/
public function isSupported(object|string $builder): bool;

/**
* @param TBuilder $builder
* @param array<string, mixed> $args
*
* @return TBuilder
*/
public function enhance(
object $builder,
mixed $root,
array $args,
GraphQLContext $context,
ResolveInfo $info,
): object;

/**
* @param TBuilder $builder
* @param int<1, max> $limit
Expand Down
108 changes: 84 additions & 24 deletions packages/graphql/src/Stream/Directives/Directive.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Illuminate\Database\Eloquent\Relations\Relation as EloquentRelation;
use Illuminate\Database\Query\Builder as QueryBuilder;
use Laravel\Scout\Builder as ScoutBuilder;
use LastDragon_ru\LaraASP\Core\Utils\Cast;
use LastDragon_ru\LaraASP\Eloquent\ModelHelper;
use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfo;
use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfoDetector;
use LastDragon_ru\LaraASP\GraphQL\Builder\Context;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\BuilderInfoProvider;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Enhancer;
use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\TypeSource;
use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\InterfaceFieldArgumentSource;
use LastDragon_ru\LaraASP\GraphQL\Builder\Sources\InterfaceFieldSource;
Expand Down Expand Up @@ -98,10 +101,10 @@ class Directive extends BaseDirective implements FieldResolver, FieldManipulator
final public const ArgKey = 'key';

/**
* @param StreamFactory<object> $streamFactory
* @param StreamFactory<object> $factory
*/
public function __construct(
private readonly StreamFactory $streamFactory,
protected readonly StreamFactory $factory,
) {
// empty
}
Expand Down Expand Up @@ -177,16 +180,6 @@ public static function definition(): string {
}
//</editor-fold>

// <editor-fold desc="Getters / Setters">
// =========================================================================
/**
* @return StreamFactory<object>
*/
protected function getStreamFactory(): StreamFactory {
return $this->streamFactory;
}
// </editor-fold>

// <editor-fold desc="FieldManipulator">
// =========================================================================
#[Override]
Expand Down Expand Up @@ -412,7 +405,7 @@ static function (mixed $argument, AstManipulator $manipulator): bool {
}

// Not supported?
if ($type !== null && !$this->getStreamFactory()->isSupported($type)) {
if ($type !== null && !$this->factory->isSupported($type)) {
throw new BuilderUnsupported($source, $type);
}
} catch (ReflectionException) {
Expand Down Expand Up @@ -441,31 +434,96 @@ public function resolveField(FieldValue $fieldValue): callable {
$offset = $this->getFieldValue(StreamOffsetDirective::class, $manipulator, $source, $info, $args);

// Builder
$factory = $this->getStreamFactory();
$resolver = $this->getResolver($source);
$builder = $resolver !== null && is_callable($resolver)
? $resolver($root, $args, $context, $info)
: null;
$builder = is_object($builder)
? $this->getBuilder($builder, $root, $args, $context, $info)
: null;

if (!is_object($builder)) {
throw new BuilderInvalid($source, gettype($builder));
} elseif (!$factory->isSupported($builder)) {
} elseif (!$this->factory->isSupported($builder)) {
throw new BuilderUnsupported($source, $builder::class);
} else {
// ok
}

// Stream
$key = $this->getArgKey($manipulator, $source);
$limit = $this->getFieldValue(StreamLimitDirective::class, $manipulator, $source, $info, $args);
$builder = $factory->enhance($builder, $root, $args, $context, $info);
$stream = $factory->create($builder, $key, $limit, $offset);
$stream = new StreamValue($stream);
$key = $this->getArgKey($manipulator, $source);
$limit = $this->getFieldValue(StreamLimitDirective::class, $manipulator, $source, $info, $args);
$stream = $this->factory->create($builder, $key, $limit, $offset);
$stream = new StreamValue($stream);

return $stream;
};
}

/**
* @param array<string, mixed> $args
*/
protected function getBuilder(
object $builder,
mixed $root,
array $args,
GraphQLContext $context,
ResolveInfo $info,
): object {
// Scout?
if ($builder instanceof EloquentBuilder) {
$enhancer = new class ($info->argumentSet, $builder) extends ScoutEnhancer {
#[Override]
public function enhanceEloquentBuilder(): ScoutBuilder {
return parent::enhanceEloquentBuilder();
}
};

if ($enhancer->canEnhanceBuilder()) {
$builder = $enhancer->enhanceEloquentBuilder();
}
}

// Lighthouse's enhancer is slower because it processes all nested fields
// of the input value, and then we need to recreate Argument from the
// plain value. So we are skipping it if possible.
$lighthouse = false;
$filter = static function (object $directive): bool {
return !($directive instanceof Enhancer)
&& !($directive instanceof Offset)
&& !($directive instanceof Limit)
&& !($directive instanceof SearchDirective);
};

foreach ($info->argumentSet->arguments as $argument) {
if ($argument->directives->contains(static fn ($directive) => !$filter($directive))) {
foreach ($argument->directives as $directive) {
if ($directive instanceof Enhancer) {
$builder = $directive->enhance($builder, $argument);
}
}
} else {
$lighthouse = true;
}
}

// Not possible?
if (
$lighthouse
&& (
$builder instanceof EloquentBuilder
|| $builder instanceof EloquentRelation
|| $builder instanceof QueryBuilder
|| $builder instanceof ScoutBuilder
)
) {
$builder = $info->enhanceBuilder($builder, [], $root, $args, $context, $info, $filter);
}

// Return
return $builder;
}

/**
* @return Closure(mixed, array<string, mixed>, GraphQLContext, ResolveInfo):mixed|array{class-string, string}|null
*/
Expand Down Expand Up @@ -493,13 +551,15 @@ protected function getResolver(ObjectFieldSource|InterfaceFieldSource $source):
}
} else {
$parent = $source->getParent()->getTypeName();
$resolver = $this->getResolverQuery($parent, $source->getName()) ?? (
RootType::isRootType($parent)
$resolver = $this->getResolverQuery($parent, $source->getName());

if (!$resolver) {
$resolver = RootType::isRootType($parent)
? $this->getResolverModel(
Container::getInstance()->make(StreamType::class)->getOriginalTypeName($source->getTypeName()),
)
: $this->getResolverRelation($parent, $source->getName())
);
: $this->getResolverRelation($parent, $source->getName());
}
}

return $resolver;
Expand Down
Loading

0 comments on commit 08f0e26

Please sign in to comment.