Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

### Changed

- Override the default field resolver `GraphQL\Executor\Executor::defaultFieldResolver()` with `Nuwave\Lighthouse\LighthouseServiceProvider::defaultFieldResolver()` https://github.com/nuwave/lighthouse/pull/2693

## v6.64.3

### Fixed
Expand Down
10 changes: 10 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ Compare your `lighthouse.php` against the latest [default configuration](src/lig

## v6 to v7

### Default field resolver changed

The default field resolver was changed from `GraphQL\Executor\Executor::defaultFieldResolver()` to `Nuwave\Lighthouse\LighthouseServiceProvider::defaultFieldResolver()`.
The new default fields resolver is expected to be mostly compatible with the previous one, but introduces some behavior changes when resolving models:

| Scenario | Previous behavior | New behavior |
|-----------------------------|---------------------------------------------------------------------------|-----------------------------------------------------------------|
| Accessing a magic attribute | Call `isset()` first, then get the value, resulting in duplicate accesses | Attempt to get the value directly, resulting in a single access |
| Accessing a PHP property | Call `isset()` first which goes into `__isset` and returns `null` | Attempt to get the value directly, returning the property value |

### Leverage automatic test trait setup

Methods you need to explicitly call to set up test traits were removed in favor of automatic setup.
Expand Down
3 changes: 2 additions & 1 deletion docs/master/digging-deeper/extending-lighthouse.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ final class SomePackageServiceProvider extends ServiceProvider

## Changing the default resolver

Lighthouse will fall back to using [webonyx's default resolver](https://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver)
Lighthouse overrides [webonyx's default resolver](https://webonyx.github.io/graphql-php/data-fetching#default-field-resolver)
for non-root fields, [see resolver precedence](../the-basics/fields.md#resolver-precedence).
See `Nuwave\Lighthouse\LighthouseServiceProvider::defaultFieldResolver()` for the implementation.

You may override this by calling `GraphQL\Executor\Executor::setDefaultFieldResolver()` in your service provider's `boot()` method.

Expand Down
36 changes: 36 additions & 0 deletions src/LighthouseServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
use GraphQL\Error\Error;
use GraphQL\Error\ProvidesExtensions;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Utils\Utils;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandlerContract;
use Illuminate\Contracts\Events\Dispatcher as EventsDispatcher;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\ServiceProvider;
Expand All @@ -33,6 +36,7 @@
use Nuwave\Lighthouse\Execution\ContextFactory;
use Nuwave\Lighthouse\Execution\ContextSerializer;
use Nuwave\Lighthouse\Execution\ErrorPool;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Execution\SingleResponse;
use Nuwave\Lighthouse\Http\Responses\ResponseStream;
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
Expand All @@ -47,6 +51,7 @@
use Nuwave\Lighthouse\Support\Contracts\CanStreamResponse;
use Nuwave\Lighthouse\Support\Contracts\CreatesContext;
use Nuwave\Lighthouse\Support\Contracts\CreatesResponse;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Nuwave\Lighthouse\Support\Contracts\ProvidesResolver;
use Nuwave\Lighthouse\Support\Contracts\ProvidesSubscriptionResolver;
use Nuwave\Lighthouse\Support\Contracts\ProvidesValidationRules;
Expand Down Expand Up @@ -146,6 +151,37 @@ public function boot(ConfigRepository $configRepository, EventsDispatcher $dispa
return new JsonResponse($serializableResult);
});
}

Executor::setDefaultFieldResolver([static::class, 'defaultFieldResolver']); // @phpstan-ignore argument.type (callable not recognized)
}

/**
* The default field resolver for GraphQL queries.
*
* This method is used to resolve fields on the object-like value returned by a resolver.
* It checks if the value is an Eloquent model and retrieves the attribute or property accordingly.
* Otherwise, it falls back to the default behavior from webonyx/graphql-php's default field resolver.
*
* @see \GraphQL\Executor\Executor::defaultFieldResolver()
*
* @param array<string, mixed> $args
*/
public static function defaultFieldResolver(mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
$fieldName = $resolveInfo->fieldName;

if ($root instanceof Model) {
$property = $root->getAttribute($fieldName);
if ($property === null && property_exists($root, $fieldName)) {
$property = $root->{$fieldName};
}
} else {
$property = Utils::extractKey($root, $fieldName);
}

return $property instanceof \Closure
? $property($root, $args, $context, $resolveInfo)
: $property;
}

protected function loadRoutesFrom($path): void
Expand Down
8 changes: 3 additions & 5 deletions tests/Integration/Models/PropertyAccessTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ public function testPhpProperty(): void
])->assertJson([
'data' => [
'user' => [
// TODO consider changing the default resolver so this returns User::PHP_PROPERTY_VALUE
'php_property' => null,
'php_property' => User::PHP_PROPERTY_VALUE,
],
],
]);
Expand Down Expand Up @@ -174,7 +173,7 @@ public function testPrefersAttributeAccessorNullThatShadowsPhpProperty(): void
])->assertJson([
'data' => [
'user' => [
'exists' => null,
'exists' => true,
],
],
]);
Expand Down Expand Up @@ -208,8 +207,7 @@ public function testExpensivePropertyIsOnlyCalledOnce(): void
])->assertJson([
'data' => [
'user' => [
// TODO consider changing the default resolver so this returns 1
'expensive_property' => 2,
'expensive_property' => 1,
],
],
]);
Expand Down