diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 67f8275d16..5066dc8463 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,7 +8,7 @@ jobs: composer-normalize: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} @@ -29,7 +29,7 @@ jobs: prettier: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} @@ -45,7 +45,7 @@ jobs: php-cs-fixer: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7a3e588d25..5056db1ede 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -30,6 +30,7 @@ jobs: laravel-version: - "^9" - "^10" + - "^11" composer: - name: lowest arg: "--prefer-lowest --prefer-stable" @@ -38,9 +39,13 @@ jobs: exclude: - php-version: "8.0" laravel-version: "^10" + - php-version: "8.0" + laravel-version: "^11" + - php-version: "8.1" + laravel-version: "^11" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: @@ -86,6 +91,7 @@ jobs: laravel-version: - "^9" - "^10" + - "^11" os: - ubuntu-latest composer: @@ -96,6 +102,10 @@ jobs: exclude: - php-version: "8.0" laravel-version: "^10" + - php-version: "8.0" + laravel-version: "^11" + - php-version: "8.1" + laravel-version: "^11" services: mysql: @@ -113,10 +123,11 @@ jobs: options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: + coverage: none extensions: ${{ env.REQUIRED_PHP_EXTENSIONS }} php-version: ${{ matrix.php-version }} @@ -129,7 +140,7 @@ jobs: - name: "Remove conflicting dependencies that are not needed here" run: composer remove --dev --no-update larastan/larastan phpstan/phpstan-mockery phpbench/phpbench rector/rector - - if: matrix.laravel-version != '^10' + - if: matrix.laravel-version != '^10' && matrix.laravel-version != '^11' run: composer remove --dev --no-update laravel/pennant - run: > @@ -169,7 +180,7 @@ jobs: options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: @@ -203,7 +214,7 @@ jobs: - "^10" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e819a1b42..86a5729e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Added + +- Support Laravel 11 https://github.com/nuwave/lighthouse/pull/2508 + ## v6.35.0 ### Added diff --git a/composer.json b/composer.json index c5a65c7bc1..51931772db 100644 --- a/composer.json +++ b/composer.json @@ -29,15 +29,15 @@ "php": "^8", "ext-json": "*", "haydenpierce/class-finder": "^0.4 || ^0.5", - "illuminate/auth": "^9 || ^10", - "illuminate/bus": "^9 || ^10", - "illuminate/contracts": "^9 || ^10", - "illuminate/http": "^9 || ^10", - "illuminate/pagination": "^9 || ^10", - "illuminate/queue": "^9 || ^10", - "illuminate/routing": "^9 || ^10", - "illuminate/support": "^9 || ^10", - "illuminate/validation": "^9 || ^10", + "illuminate/auth": "^9 || ^10 || ^11", + "illuminate/bus": "^9 || ^10 || ^11", + "illuminate/contracts": "^9 || ^10 || ^11", + "illuminate/http": "^9 || ^10 || ^11", + "illuminate/pagination": "^9 || ^10 || ^11", + "illuminate/queue": "^9 || ^10 || ^11", + "illuminate/routing": "^9 || ^10 || ^11", + "illuminate/support": "^9 || ^10 || ^11", + "illuminate/validation": "^9 || ^10 || ^11", "laragraph/utils": "^1.5 || ^2", "thecodingmachine/safe": "^1 || ^2", "webonyx/graphql-php": "^15" @@ -50,23 +50,22 @@ "fakerphp/faker": "^1.21", "google/protobuf": "^3.21", "larastan/larastan": "^2.6.1", - "laravel/framework": "^9 || ^10", + "laravel/framework": "^9 || ^10 || ^11", "laravel/legacy-factories": "^1.1.1", - "laravel/lumen-framework": "^9 || ^10 || dev-master", "laravel/pennant": "^1", - "laravel/scout": "^8 || ^9 || ^10", + "laravel/scout": "^8 || ^9 || ^10 || ^11", "mattiasgeniar/phpunit-query-count-assertions": "^1.1", "mll-lab/graphql-php-scalars": "^6", "mll-lab/php-cs-fixer-config": "^5", "mockery/mockery": "^1.5", "nesbot/carbon": "^2.62.1", - "orchestra/testbench": "^7.7 || ^8.8", + "orchestra/testbench": "^7.7 || ^8.8 || ^9", "phpbench/phpbench": "^1.2.6", "phpstan/extension-installer": "^1", "phpstan/phpstan": "^1.10.3", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-phpunit": "^1.1.1", - "phpunit/phpunit": "^9.6.4 || ^10", + "phpunit/phpunit": "^9.6.4 || ^10 || ^11", "predis/predis": "^1.1 || ^2.1", "pusher/pusher-php-server": "^5 || ^6 || ^7.0.2", "rector/rector": "^1", @@ -74,7 +73,6 @@ }, "suggest": { "ext-protobuf": "Improve protobuf serialization performance (used for tracing)", - "bensampo/laravel-enum": "Convenient enum definitions that can easily be registered in your Schema", "google/protobuf": "Required when using the tracing driver federated-tracing", "laravel/pennant": "Required for the @feature directive", "laravel/scout": "Required for the @search directive", diff --git a/phpstan.neon b/phpstan.neon index f271bbbe53..20e1f9b2c7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -20,6 +20,8 @@ parameters: - src/Pennant - tests/Integration/Pennant - src/Tracing/FederatedTracing/Proto # Generated classes from protobuf + # Ignore errors caused by the absence of Lumen in the dev dependencies + - tests/Unit/Testing/TestingTraitDummyLumen.php ignoreErrors: # PHPStan does not get it - '#Parameter \#1 \$callback of static method Closure::fromCallable\(\) expects callable\(\): mixed, array{object, .*} given\.#' @@ -64,3 +66,32 @@ parameters: # Older versions of bensampo/laravel-enum are not generic yet - '#contains generic type BenSampo\\Enum\\Enum<.+> but class BenSampo\\Enum\\Enum is not generic\.#' + + # Ignore errors caused by the absence of Lumen in the dev dependencies + - path: src/Support/AppVersion.php + message: '#PHPDoc tag @var for variable \$container contains unknown class Laravel\\Lumen\\Application.#' + - path: src/Support/AppVersion.php + message: '#Call to method version\(\) on an unknown class Laravel\\Lumen\\Application.#' + - path: src/Subscriptions/SubscriptionRouter.php + messages: + - '#Parameter \$router of method Nuwave\\Lighthouse\\Subscriptions\\SubscriptionRouter::pusher\(\) has invalid type Laravel\\Lumen\\Routing\\Router\.#' + - '#Call to method post\(\) on an unknown class Laravel\\Lumen\\Routing\\Router\.#' + - '#Parameter \$router of method Nuwave\\Lighthouse\\Subscriptions\\SubscriptionRouter::echoRoutes\(\) has invalid type Laravel\\Lumen\\Routing\\Router\.#' + - path: src/Http/routes.php + messages: + - '#PHPDoc tag @var for variable \$router contains unknown class Laravel\\Lumen\\Routing\\Router\.#' + - '#Call to method addRoute\(\) on an unknown class Laravel\\Lumen\\Routing\\Router\.#' + + # Recent Mockery versions do not account for the last arg possibly being a closure + - path: tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php + message: '#Parameter \#2 \.\.\.\$args of static method Mockery::mock\(\) expects array\|class-string\|Nuwave\\Lighthouse\\Subscriptions\\SubscriptionGuard, Closure given\.#' + + - path: tests/Integration/Schema/Directives/BuilderDirectiveTest.php + message: '#Return type of call to static method Mockery::mock\(\) contains unresolvable type\.#' + + # Laravel added generics to Arr::first in 11.x, not sure how to handle them for now. + - path: src/OrderBy/OrderByDirective.php + message: '#Unable to resolve the template type TValue in call to method static method Illuminate\\Support\\Arr::first\(\)#' + - path: src/Testing/TestResponseMixin.php + message: '#Unable to resolve the template type TValue in call to method static method Illuminate\\Support\\Arr::first\(\)#' + diff --git a/src/Exceptions/ClientSafeModelNotFoundException.php b/src/Exceptions/ClientSafeModelNotFoundException.php index 8e50418c5a..4ce19c3d54 100644 --- a/src/Exceptions/ClientSafeModelNotFoundException.php +++ b/src/Exceptions/ClientSafeModelNotFoundException.php @@ -7,6 +7,7 @@ /** * @template TModel of \Illuminate\Database\Eloquent\Model + * * @extends ModelNotFoundException */ class ClientSafeModelNotFoundException extends ModelNotFoundException implements ClientAware @@ -18,6 +19,7 @@ public function isClientSafe(): bool /** * @param ModelNotFoundException $laravelException + * * @return self */ public static function fromLaravel(ModelNotFoundException $laravelException): self diff --git a/src/Schema/Directives/ThrottleDirective.php b/src/Schema/Directives/ThrottleDirective.php index fd7fb714f9..15ae23c0f1 100644 --- a/src/Schema/Directives/ThrottleDirective.php +++ b/src/Schema/Directives/ThrottleDirective.php @@ -83,7 +83,8 @@ public function handleField(FieldValue $fieldValue): void $this->handleLimit( sha1($name . $limit->key), $limit->maxAttempts, - $limit->decayMinutes, + // Laravel 11 switched to using seconds + $limit->decayMinutes ?? $limit->decaySeconds * 60, "{$resolveInfo->parentType}.{$resolveInfo->fieldName}", ); } diff --git a/src/Tracing/FederatedTracing/Proto/FieldStat.php b/src/Tracing/FederatedTracing/Proto/FieldStat.php index e189b9c251..eeeaa606d2 100644 --- a/src/Tracing/FederatedTracing/Proto/FieldStat.php +++ b/src/Tracing/FederatedTracing/Proto/FieldStat.php @@ -90,7 +90,7 @@ class FieldStat extends \Google\Protobuf\Internal\Message * field_execution_weight). * @var int|string $observed_execution_count * Number of times that the resolver for this field is directly observed being - * executed. + * executed * @var int|string $estimated_execution_count * Same as `observed_execution_count` but potentially scaled upwards if the server was only * performing field-level instrumentation on a sampling of operations. For diff --git a/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php index 7fb49e3c03..7c1835a58d 100644 --- a/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php +++ b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php @@ -107,7 +107,7 @@ class QueryLatencyStats extends \Google\Protobuf\Internal\Message * @var int|string $persisted_query_misses * @var array|array|\Google\Protobuf\Internal\RepeatedField $cache_latency_count * This array includes the latency buckets for all operations included in cache_hits - * See comment on latency_count for details. + * See comment on latency_count for details * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats $root_error_stats * Paths and counts for each error. The total number of requests with errors within this object should be the same as * requests_with_errors_count below. diff --git a/src/Tracing/FederatedTracing/Proto/Trace.php b/src/Tracing/FederatedTracing/Proto/Trace.php index 3af1446ad7..6748c97c2e 100644 --- a/src/Tracing/FederatedTracing/Proto/Trace.php +++ b/src/Tracing/FederatedTracing/Proto/Trace.php @@ -170,15 +170,15 @@ class Trace extends \Google\Protobuf\Internal\Message * Optional. Data for populating the Message object. * * @var \Google\Protobuf\Timestamp $start_time - * Wallclock time when the trace began. + * Wallclock time when the trace began * @var \Google\Protobuf\Timestamp $end_time - * Wallclock time when the trace ended. + * Wallclock time when the trace ended * @var int|string $duration_ns * High precision duration of the trace; may not equal end_time-start_time - * (eg, if your machine's clock changed during the trace). + * (eg, if your machine's clock changed during the trace) * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node $root * A tree containing information about all resolvers run directly by this - * service, including errors. + * service, including errors * @var bool $is_incomplete * If this is true, the trace is potentially missing some nodes that were * present on the query plan. This can happen if the trace span buffer used @@ -201,7 +201,7 @@ class Trace extends \Google\Protobuf\Internal\Message * @var string $unexecutedOperationBody * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields * can include reference to the operation being sent for users to dig into the set of operations - * that are failing validation. + * that are failing validation * @var string $unexecutedOperationName * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details $details * @var string $client_name diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php index f1a4a029fa..edc48c8653 100644 --- a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php @@ -66,12 +66,12 @@ class FetchNode extends \Google\Protobuf\Internal\Message * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace $trace * This Trace only contains start_time, end_time, duration_ns, and root; * all timings were calculated **on the subgraph**, and clock skew - * will be handled by the ingress server. + * will be handled by the ingress server * @var int|string $sent_time_offset - * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway * @var \Google\Protobuf\Timestamp $sent_time * Wallclock times measured in the Router/Gateway for when this operation was - * sent and received. + * sent and received * @var \Google\Protobuf\Timestamp $received_time * } */ diff --git a/tests/Integration/Auth/CanDirectiveDBTest.php b/tests/Integration/Auth/CanDirectiveDBTest.php index fbfbf5e6ee..b5a498238d 100644 --- a/tests/Integration/Auth/CanDirectiveDBTest.php +++ b/tests/Integration/Auth/CanDirectiveDBTest.php @@ -93,6 +93,7 @@ public function testFailsToFindSpecificModel(): void ], ]); } + public function testThrowsCustomExceptionWhenFailsToFindModel(): void { $user = new User(); @@ -129,7 +130,7 @@ public function testThrowsCustomExceptionWhenFailsToFindModel(): void $previous = $error->getPrevious(); $this->assertNotNull($previous); - $this->assertSame(ClientSafeModelNotFoundException::class, get_class($previous)); + $this->assertInstanceOf(ClientSafeModelNotFoundException::class, $previous); } } diff --git a/tests/Integration/Auth/CanFindDirectiveDBTest.php b/tests/Integration/Auth/CanFindDirectiveDBTest.php index 36059ee872..1a93535d34 100644 --- a/tests/Integration/Auth/CanFindDirectiveDBTest.php +++ b/tests/Integration/Auth/CanFindDirectiveDBTest.php @@ -166,7 +166,7 @@ public function testThrowsCustomExceptionWhenFailsToFindModel(): void $previous = $error->getPrevious(); $this->assertNotNull($previous); - $this->assertSame(ClientSafeModelNotFoundException::class, get_class($previous)); + $this->assertInstanceOf(ClientSafeModelNotFoundException::class, $previous); } } diff --git a/tests/Integration/Schema/Types/InterfaceTest.php b/tests/Integration/Schema/Types/InterfaceTest.php index f608184dbd..673d0782df 100644 --- a/tests/Integration/Schema/Types/InterfaceTest.php +++ b/tests/Integration/Schema/Types/InterfaceTest.php @@ -4,7 +4,6 @@ use GraphQL\Type\Definition\Type; use Illuminate\Container\Container; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Collection; use Nuwave\Lighthouse\Schema\TypeRegistry; use Tests\DBTestCase; @@ -341,11 +340,11 @@ interface HasPosts { $this->assertSame('PostPaginator', $result->json('data.__type.fields.0.type.ofType.name')); } - /** @return \Illuminate\Database\Eloquent\Collection */ - public static function fetchResults(): EloquentCollection + /** @return \Illuminate\Support\Collection */ + public static function fetchResults(): Collection { - /** @var \Illuminate\Database\Eloquent\Collection $results */ - $results = new EloquentCollection(); + /** @var \Illuminate\Support\Collection $results */ + $results = new Collection(); return $results ->concat(User::all()) diff --git a/tests/Integration/Schema/Types/UnionTest.php b/tests/Integration/Schema/Types/UnionTest.php index 7e513d8df7..a0e4596cad 100644 --- a/tests/Integration/Schema/Types/UnionTest.php +++ b/tests/Integration/Schema/Types/UnionTest.php @@ -3,7 +3,7 @@ namespace Tests\Integration\Schema\Types; use GraphQL\Error\InvariantViolation; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Support\Collection; use Nuwave\Lighthouse\Schema\TypeRegistry; use Tests\DBTestCase; use Tests\Utils\Models\Post; @@ -178,11 +178,11 @@ public function testThrowsOnNonOverlappingSchemaMapping(): void '); } - /** @return \Illuminate\Database\Eloquent\Collection */ - public static function fetchResults(): EloquentCollection + /** @return \Illuminate\Support\Collection */ + public static function fetchResults(): Collection { - /** @var \Illuminate\Database\Eloquent\Collection $results */ - $results = new EloquentCollection(); + /** @var \Illuminate\Support\Collection $results */ + $results = new Collection(); return $results ->concat(User::all()) diff --git a/tests/Unit/Schema/Directives/DeprecatedDirectiveTest.php b/tests/Unit/Schema/Directives/DeprecatedDirectiveTest.php index bab17f7a38..afb6c58884 100644 --- a/tests/Unit/Schema/Directives/DeprecatedDirectiveTest.php +++ b/tests/Unit/Schema/Directives/DeprecatedDirectiveTest.php @@ -58,7 +58,7 @@ enumValues(includeDeprecated: $includeDeprecated) { $types = $withoutDeprecatedIntrospection->json('data.__schema.types'); $foo = Arr::first($types, static fn (array $type): bool => $type['name'] === 'Foo'); - $this->assertCount(1, $foo['enumValues']); + $this->assertCount(1, $foo['enumValues'] ?? 0); $includeDeprecatedIntrospection = $this->graphQL( $introspectionQuery, @@ -85,6 +85,6 @@ enumValues(includeDeprecated: $includeDeprecated) { $types = $includeDeprecatedIntrospection->json('data.__schema.types'); $foo = Arr::first($types, static fn (array $type): bool => $type['name'] === 'Foo'); - $this->assertCount(2, $foo['enumValues']); + $this->assertCount(2, $foo['enumValues'] ?? 0); } } diff --git a/tests/Unit/Schema/Directives/HasManyDirectiveTest.php b/tests/Unit/Schema/Directives/HasManyDirectiveTest.php index d70a415b64..6688eae8b4 100644 --- a/tests/Unit/Schema/Directives/HasManyDirectiveTest.php +++ b/tests/Unit/Schema/Directives/HasManyDirectiveTest.php @@ -49,7 +49,7 @@ public function testUsesEdgeTypeForRelayConnections(): void ); $this->assertSame( $expectedConnectionName, - $tasks['type']['ofType']['name'], + $tasks['type']['ofType']['name'] ?? 0, ); } diff --git a/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php b/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php index c9d7600dcf..d8584e5802 100644 --- a/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php +++ b/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php @@ -91,19 +91,24 @@ public function getAuthIdentifier() return $this->id; } - public function getAuthPassword() + public function getAuthPassword(): string { return ''; } - public function getRememberToken() + public function getAuthPasswordName(): string + { + return ''; + } + + public function getRememberToken(): string { return ''; } public function setRememberToken($value): void {} - public function getRememberTokenName() + public function getRememberTokenName(): string { return ''; }