This document provides guidance for upgrading between major versions of Lighthouse.
The configuration options often change between major versions.
Compare your lighthouse.php
against the latest default configuration.
Lighthouse previously allowed passing a map with arbitrary keys as the messages
argument on @rules
and @rulesForArray
. Such a construct is impossible to define
within the directive definition and leads to static validation errors.
@rules(
apply: ["max:280"],
- messages: {
- max: "Tweets have a limit of 280 characters"
- }
+ messages: [
+ {
+ rule: "max"
+ message: "Tweets have a limit of 280 characters"
+ }
+ ]
)
The @delete
, @forceDelete
and @restore
directives no longer offer the
globalId
argument. Use @globalId
on the argument instead.
type Mutation {
- deleteUser(id: ID!): User! @delete(globalId: true)
+ deleteUser(id: ID! @globalId): User! @delete
}
Due to Lighthouse's ongoing effort to provide static schema validation,
the with
argument of @guard
must now be provided as a list of strings.
type Mutation {
- somethingSensitive: Boolean @guard(with: "api")
+ somethingSensitive: Boolean @guard(with: ["api"])
}
The following versions are now the minimal required versions:
- PHP 7.2
- Laravel 5.6
- PHPUnit 7
Parts of the final schema are automatically generated by Lighthouse. Clients that depend on specific fields or type names may have to adapt. The recommended process for finding breaking changes is:
- Print your schema before upgrading:
php artisan lighthouse:print-schema > old.graphql
- Upgrade, then re-print your schema:
php artisan lighthouse:print-schema > new.graphql
- Use graphql-inspector to compare your
changes:
graphql-inspector diff old.graphql new.graphql
Field resolver classes now only support the method name __invoke
, using
the name resolve
no longer works.
namespace App\GraphQL\Queries;
class SomeField
{
- public function resolve(...
+ public function __invoke(...
The @middleware
directive has been removed, as it violates the boundary between HTTP and GraphQL
request handling.
Authentication is one of most common use cases for @middleware
. You can now use
the @guard directive on selected fields.
type Query {
- profile: User! @middleware(checks: ["auth"])
+ profile: User! @guard
}
Note that @guard does not log in users.
To ensure the user is logged in, add the AttemptAuthenticate
middleware to your lighthouse.php
middleware config, see the default config for an example.
Other functionality can be replaced by a custom FieldMiddleware
directive. Just like Laravel Middleware, it can wrap around individual field resolvers.
The interface \Nuwave\Lighthouse\Support\Contracts\Directive
now has the same functionality
as the removed \Nuwave\Lighthouse\Support\Contracts\DefinedDirective
. If you previously
implemented DefinedDirective
, remove it from your directives:
-use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
-class TrimDirective extends BaseDirective implements ArgTransformerDirective, DefinedDirective
+class TrimDirective extends BaseDirective implements ArgTransformerDirective
Instead of just providing the name of the directive, all directives must now return an SDL definition that formally describes them.
- public function name()
- {
- return 'trim';
- }
+ /**
+ * Formal directive specification in schema definition language (SDL).
+ *
+ * @return string
+ */
+ public static function definition(): string
+ {
+ return /** @lang GraphQL */ <<<'GRAPHQL'
+"""
+A description of what this directive does.
+"""
+directive @trim(
+ """
+ Directives can have arguments to parameterize them.
+ """
+ someArg: String
+) on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
+GRAPHQL;
+ }
The argument to specify the column to order by when using @orderBy
was renamed
to column
to match the @whereConditions
directive.
Client queries will have to be changed like this:
{
posts (
orderBy: [
{
- field: POSTED_AT
+ column: POSTED_AT
order: ASC
}
]
) {
title
}
}
If you absolutely cannot break your clients, you can re-implement @orderBy
in your
project - it is a relatively simple ArgManipulator
directive.
The @model
directive was repurposed to take the place of @modelClass
. As a replacement
for the current functionality of @model
, the new @node
directive was added,
see nuwave#974 for details.
You can adapt to this change in two refactoring steps that must be done in order:
-
Rename all usages of
@model
to@node
, e.g.:-type User @model { +type User @node { id: ID! @globalId }
-
Rename all usages of
@modelClass
to@model
, e.g.-type PaginatedPost @modelClass(class: "\\App\\Post") { +type PaginatedPost @model(class: "\\App\\Post") { id: ID! }
The new @hash
directive is also used for password hashing, but respects the
configuration settings of your Laravel project.
type Mutation {
createUser(
name: String!
- password: String! @bcrypt
+ password: String! @hash
): User!
}
Instead of passing down the usual resolver arguments, the @method
directive will
now pass just the arguments given to a field. This behaviour could previously be
enabled through the passOrdered
option, which is now removed.
type User {
purchasedItemsCount(year: Int!, includeReturns: Boolean): Int @method
}
The method will have to change like this:
-public function purchasedItemsCount($root, array $args)
+public function purchasedItemsCount(int $year, ?bool $includeReturns)
This affects custom directives that implemented one of the following interfaces:
\Nuwave\Lighthouse\Support\Contracts\ArgDirectiveForArray
\Nuwave\Lighthouse\Support\Contracts\ArgTransformerDirective
\Nuwave\Lighthouse\Support\Contracts\ArgBuilderDirective
Whereas those interfaces previously extended \Nuwave\Lighthouse\Support\Contracts\ArgDirective
, you now
have to choose if you want them to apply to entire lists of arguments, elements within that list, or both.
Change them as follows to make them behave like in v4:
+use Nuwave\Lighthouse\Support\Contracts\ArgDirective;
use Nuwave\Lighthouse\Support\Contracts\ArgTransformerDirective;
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
-class MyCustomArgDirective extends BaseDirective implements ArgTransformerDirective, DefinedDirective
+class MyCustomArgDirective extends BaseDirective implements ArgTransformerDirective, DefinedDirective, ArgDirective
The application of directives that implement the ArgDirective
interface is
split into three distinct phases:
- Sanitize: Clean the input, e.g. trim whitespace.
Directives can hook into this phase by implementing
ArgSanitizerDirective
. - Validate: Ensure the input conforms to the expectations, e.g. check a valid email is given
- Transform: Change the input before processing it further, e.g. hashing passwords.
Directives can hook into this phase by implementing
ArgTransformerDirective
The ValidationDirective
abstract class was removed in favour of validator classes.
They represent a more lightweight way and flexible way to reuse complex validation rules,
not only on fields but also on input objects.
To convert an existing custom validation directive to a validator class, change it as follows:
<?php
-namespace App\GraphQL\Directives;
+namespace App\GraphQL\Validators;
use Illuminate\Validation\Rule;
-use Nuwave\Lighthouse\Schema\Directives\ValidationDirective;
+use Nuwave\Lighthouse\Validation\Validator;
-class UpdateUserValidationDirective extends ValidationDirective
+class UpdateUserValidator extends Validator
{
/**
* @return array<string, array<mixed>>
*/
public function rules(): array
{
return [
'id' => ['required'],
'name' => ['sometimes', Rule::unique('users', 'name')->ignore($this->args['id'], 'id')],
];
}
}
Instead of directly using this class as a directive, place the @validator
directive on your field.
type Mutation {
- updateUser(id: ID, name: String): User @update @updateUserValidation
+ updateUser(id: ID, name: String): User @update @validator
}
The event is no longer fired, and the event class was removed. Lighthouse now uses a queued job instead.
If you manually fired the event, replace it by queuing a Nuwave\Lighthouse\Subscriptions\BroadcastSubscriptionJob
or a call to Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions::queueBroadcast()
.
In case you depend on an event being fired whenever a subscription is queued, you can bind your
own implementation of Nuwave\Lighthouse\Subscriptions\Contracts\BroadcastsSubscriptions
.
Calling register()
on the \Nuwave\Lighthouse\Schema\TypeRegistry
now throws when passing
a type that was already registered, as this most likely is an error.
If you want to previous behaviour of overwriting existing types, use overwrite()
instead.
$typeRegistry = app(\Nuwave\Lighthouse\Schema\TypeRegistry::class);
-$typeRegistry->register($someType);
+$typeRegistry->overwrite($someType);
Since GraphQL constrains allowed inputs by design, mass assignment protection is not needed.
By default, Lighthouse will use forceFill()
when populating a model with arguments in mutation directives.
This allows you to use mass assignment protection for other cases where it is actually useful.
If you need to revert to the old behavior of using fill()
, you can change your lighthouse.php
:
- 'force_fill' => true,
+ 'force_fill' => false,
Collecting partial errors is now done through the singleton \Nuwave\Lighthouse\Execution\ErrorPool
instead of \Nuwave\Lighthouse\Execution\ErrorBuffer
:
try {
// Something that might fail but still allows for a partial result
} catch (\Throwable $error) {
$errorPool = app(\Nuwave\Lighthouse\Execution\ErrorPool::class);
$errorPool->record($error);
}
return $result;
The TestResponse::jsonGet()
mixin was removed in favor of the ->json()
method,
natively supported by Laravel starting from version 5.6.
$response = $this->graphQL(...);
-$response->jsonGet(...);
+$response->json(...);
The native parser from webonyx/graphql-php now supports partial parsing.
-use Nuwave\Lighthouse\Schema\AST\PartialParser;
+use GraphQL\Language\Parser;
Most methods work the same:
-PartialParser::directive(/** @lang GraphQL */ '@deferrable')
+Parser::constDirective(/** @lang GraphQL */ '@deferrable')
A few are different:
-PartialParser::listType("[$restrictedOrderByName!]");
+Parser::typeReference("[$restrictedOrderByName!]");
-PartialParser::inputValueDefinitions([$foo, $bar]);
+Parser::inputValueDefinition($foo);
+Parser::inputValueDefinition($bar);
Since the addition of the HAS
input in whereCondition
mechanics,
there has to be a default operator for the HAS
input.
If you implement your own custom operator, implement defaultHasOperator
.
For example, this is the implementation of the default \Nuwave\Lighthouse\WhereConditions\SQLOperator
:
public function defaultHasOperator(): string
{
return 'GTE';
}
If you implemented your own error handler, change it like this:
use Nuwave\Lighthouse\Execution\ErrorHandler;
class ExtensionErrorHandler implements ErrorHandler
{
- public static function handle(Error $error, Closure $next): array
+ public function __invoke(?Error $error, Closure $next): ?array
{
...
}
}
You can now discard errors by returning null
from the handler.
If you use complex where condition directives, such as @whereConditions
,
upgrade mll-lab/graphql-php-scalars
to v4:
composer require mll-lab/graphql-php-scalars:^4