Skip to content

Commit 0335865

Browse files
committed
docs: add long-lived-worker concurrency notes + cut 8.3.0 docs version
- troubleshooting: document the cold-build type-registry race ("Cached type in registry..." / duplicate named types) and the warm-or-lock fix for Swoole/RoadRunner/FrankenPHP workers - internals: cross-reference the RecursiveTypeMapper identity invariant - query-plan: fix unbalanced parens in the getFieldSelection() example - generate the previously-missing 8.3.0 versioned docs snapshot
1 parent 2e94838 commit 0335865

46 files changed

Lines changed: 5914 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

website/docs/internals.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "ATy
113113

114114
Since "B" *is a* "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".
115115

116+
The `RecursiveTypeMapper` also enforces a per-name identity invariant on the type registry: a given GraphQL type name must
117+
resolve to a single type instance. In long-lived or coroutine runtimes (Swoole, RoadRunner, FrankenPHP) a concurrent
118+
first-request build can violate it and surface the `Cached type in registry is not the type returned by type mapper.`
119+
error — see [Troubleshooting](troubleshooting.md) for the cause and fix.
120+
116121
## Parameter mapper middlewares
117122

118123
"Parameter middlewares" are used to decide what argument should be injected into a parameter.

website/docs/query-plan.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class ProductsController
5151
#[Query]
5252
public function products(ResolveInfo $info): array
5353
{
54-
if (isset($info->getFieldSelection()['manufacturer']) {
54+
if (isset($info->getFieldSelection()['manufacturer'])) {
5555
// Let's perform a request with a JOIN on manufacturer
5656
} else {
5757
// Let's perform a request without a JOIN on manufacturer

website/docs/troubleshooting.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,41 @@ GraphQLite controllers in the Symfony controller namespace (`App\Controller` by
2222
object type-hinted in a method of a controller is a service ([because all controllers are tagged with the "controller.service_arguments" tag](https://symfony.com/doc/current/service_container/3.3-di-changes.html#controllers-are-registered-as-services))
2323

2424
To fix this issue, do not put your GraphQLite controller in the same namespace as the Symfony controllers and
25-
reconfigure your `config/graphqlite.yml` file to point to your new namespace.
25+
reconfigure your `config/graphqlite.yml` file to point to your new namespace.
26+
27+
28+
**Error: Cached type in registry is not the type returned by type mapper.**
29+
30+
**Schema must contain unique named types but contains multiple types named "X".**
31+
32+
You are most likely running GraphQLite inside a long-lived / coroutine PHP runtime (Swoole, RoadRunner, FrankenPHP) where
33+
a single worker serves many requests. These errors appear only on the **first request(s) after a cold worker boot**
34+
every subsequent request on that worker is fine.
35+
36+
In a long-lived runtime the in-memory `TypeRegistry` and type caches persist across requests, which is exactly what makes
37+
them fast. But the *first* schema build is expensive and does real filesystem I/O (class discovery, PSR-16 file-cache
38+
reads, docblock/annotation reading). Under coroutine concurrency — for example a client firing two introspection queries
39+
at once, or simply two concurrent first requests — the runtime can yield between a type cache **miss** check and the
40+
subsequent cache **write**. Two builders then construct duplicate type instances for the same GraphQL type name: the
41+
first error is GraphQLite's `RecursiveTypeMapper` tripping its identity invariant, and the second is webonyx/graphql-php
42+
rejecting the duplicate named types when assembling the schema.
43+
44+
The fix is to **single-flight the first schema build** so that the registry is fully warm before any concurrent request
45+
can race it. The simplest approach is to warm the schema once at worker bootstrap, before serving traffic — build it and
46+
force the full type map to resolve while the worker is still single-threaded (for instance in an `OnWorkerStart` /
47+
bootstrap hook):
48+
49+
```php
50+
$schema = $factory->createSchema();
51+
$schema->getTypeMap(); // forces every lazy type to resolve while single-threaded
52+
```
53+
54+
Calling `$schema->assertValid()` instead also fully populates the registry. Once the registry is warm, every later
55+
request reuses it and the race window never opens again.
56+
57+
If you cannot warm at bootstrap, guard the first build with a lock so that only one coroutine/worker builds the schema
58+
while the others wait, then all reuse the warm registry.
59+
60+
Note that a per-request cache check is **not** sufficient on its own: the build yields between the check and the write,
61+
so the type cache is only authoritative once the first build has completed end to end without interruption. That is why
62+
the fix lives at the application/runtime boundary (warm or lock) rather than in a per-call cache re-check.
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
---
2+
id: changelog
3+
title: Changelog
4+
sidebar_label: Changelog
5+
---
6+
7+
## >8.0.0
8+
9+
**For all future changelog details, refer to the [Releases](https://github.com/thecodingmachine/graphqlite/releases).
10+
This CHANGELOG.md will no longer be independently maintained.**
11+
12+
## 8.0.0
13+
14+
### Breaking Changes
15+
16+
- [#677 Drops support for Doctrine annotations](https://github.com/thecodingmachine/graphqlite/pull/677) @fogrye
17+
18+
### Improvements
19+
20+
- [#668 Adds working examples to docs](https://github.com/thecodingmachine/graphqlite/pull/668) @shish
21+
- [#698 Performance optimizations and caching in development environments (`devMode()`)](https://github.com/thecodingmachine/graphqlite/pull/698) @oprypkhantc]
22+
23+
### Bug Fixes
24+
25+
- [#702 Fix prefetching for nested fields](https://github.com/thecodingmachine/graphqlite/pull/702) @sudevva
26+
27+
### Minor Changes
28+
29+
- [#695 Removes dependecy to unmaintained thecodingmachine/cache-utils dependency](https://github.com/thecodingmachine/graphqlite/pull/695) @xyng
30+
- [#712 Caching improvements with use of multiple ClassFinders](https://github.com/thecodingmachine/graphqlite/pull/712) @andrew-demb
31+
32+
## 7.1.0
33+
34+
### Breaking Changes
35+
36+
- #698 Removes some methods and classes, namely:
37+
- Deprecated `SchemaFactory::addControllerNamespace()` and `SchemaFactory::addTypeNamespace()` in favor of `SchemaFactory::addNamespace()`
38+
- Deprecated `SchemaFactory::setGlobTTL()` in favor of `SchemaFactory::devMode()` and `SchemaFactory::prodMode()`
39+
- Removed `FactoryContext::get*TTL()` and `RootTypeMapperFactoryContext::get*TTL()` as GraphQLite no longer uses TTLs to invalidate caches
40+
- Removed `StaticClassListTypeMapper` in favor of `ClassFinderTypeMapper` used with `StaticClassFinder`
41+
- Renamed `GlobTypeMapper` to `ClassFinderTypeMapper`
42+
- Renamed `SchemaFactory::setClassBoundCacheContractFactory()` to `SchemaFactory::setClassBoundCache()`,
43+
`FactoryContext::getClassBoundCacheContractFactory()` to `FactoryContext::getClassBoundCache()` and changed their signatures
44+
- Removed `RootTypeMapperFactoryContext::getTypeNamespaces()` in favor of `RootTypeMapperFactoryContext::getClassFinder()`
45+
46+
### Improvements
47+
48+
- #698 Performance optimizations and caching in development environments (`devMode()`). @oprypkhantc
49+
50+
## 7.0.0
51+
52+
### Breaking Changes
53+
54+
- #664 Replaces [thecodingmachine/class-explorer](https://github.com/thecodingmachine/class-explorer) with [kcs/class-finder](https://github.com/alekitto/class-finder) resulting in the `SchemaFactory::setClassNameMapper` being renamed to `SchemaFactory::setFinder`. This now expects an instance of `Kcs\ClassFinder\Finder` instead of `Kcs\ClassFinder\Finder\FinderInterface`. @fogrye
55+
56+
### New Features
57+
58+
- #649 Adds support for `subscription` operations. @oojacoboo
59+
- #612 Automatic query complexity analysis. @oprypkhantc
60+
- #611 Automatic persisted queries. @oprypkhantc
61+
62+
### Improvements
63+
64+
- #658 Improves on prefetching for nested fields. @grynchuk
65+
- #646 Improves exception handling during schema parsing. @fogrye
66+
- #636 Allows the use of middleware on construtor params/fields. @oprypkhantc
67+
- #623 Improves support for description arguments on types/fields. @downace
68+
- #628 Properly handles `@param` annotations for generics support on field annotated constructor arguments. @oojacoboo
69+
- #584 Immutability improvements across the codebase. @oprypkhantc
70+
- #588 Prefetch improvements. @oprpkhantc
71+
- #606 Adds support for phpdoc descriptions and deprecation annotations on native enums. @mdoelker
72+
- Thanks to @shish, @cvergne and @mshapovalov for updating the docs!
73+
74+
### Minor Changes
75+
76+
- #639 Added support for Symfony 7. @janatjak
77+
78+
79+
## 6.2.3
80+
81+
Adds support for `Psr\Container` 1.1 with #601
82+
83+
## 6.2.2
84+
85+
This is a very simple release. We support Doctrine annotation 1.x and we've deprecated `SchemaFactory::setDoctrineAnnotationReader` in favor of native PHP attributes.
86+
87+
## 6.2.1
88+
89+
- Added support for new `Void` return types, allowing use of `void` from operation resolvers. #574
90+
- Improvements with authorization middleware #571
91+
- Updated vendor dependencies: #580 #558
92+
93+
## 6.2.0
94+
95+
Lots of little nuggets in this release! We're now targeting PHP ^8.1 and have testing on 8.2.
96+
97+
- Better support for union types and enums: #530, #535, #561, #570
98+
- Various bug and interface fixes: #532, #575, #564
99+
- GraphQL v15 required: #542
100+
- Lots of codebase improvements, more strict typing: #548
101+
102+
A special thanks to @rusted-love and @oprypkhantc for their contributions.
103+
104+
## 6.1.0
105+
106+
A shoutout to @bladl for his work on this release, improving the code for better typing and PHP 8.0 syntax updates!
107+
108+
### Breaking Changes
109+
110+
- #518 PSR-11 support now requires version 2
111+
- #508 Due to some of the code improvements, additional typing has been added to some interfaces/classes. For instance, `RootTypeMapperInterface::toGraphQLOutputType` and `RootTypeMapperInterface::toGraphQLInputType` now have the following signatures:
112+
113+
```php
114+
/**
115+
* @param (OutputType&GraphQLType)|null $subType
116+
*
117+
* @return OutputType&GraphQLType
118+
*/
119+
public function toGraphQLOutputType(
120+
Type $type,
121+
OutputType|null $subType,
122+
ReflectionMethod|ReflectionProperty $reflector,
123+
DocBlock $docBlockObj
124+
): OutputType;
125+
126+
/**
127+
* @param (InputType&GraphQLType)|null $subType
128+
*
129+
* @return InputType&GraphQLType
130+
*/
131+
public function toGraphQLInputType(
132+
Type $type,
133+
InputType|null $subType,
134+
string $argumentName,
135+
ReflectionMethod|ReflectionProperty $reflector,
136+
DocBlock $docBlockObj
137+
): InputType;
138+
```
139+
140+
### Improvements
141+
142+
- #510
143+
- #508
144+
145+
## 5.0.0
146+
147+
### Dependencies
148+
149+
- Upgraded to using version 14.9 of [webonyx/graphql-php](https://github.com/webonyx/graphql-php)
150+
151+
## 4.3.0
152+
153+
### Breaking change
154+
155+
- The method `setAnnotationCacheDir($directory)` has been removed from the `SchemaFactory`. The annotation
156+
cache will use your `Psr\SimpleCache\CacheInterface` compliant cache handler set through the `SchemaFactory`
157+
constructor.
158+
159+
### Minor changes
160+
161+
- Removed dependency for doctrine/cache and unified some of the cache layers following a PSR interface.
162+
- Cleaned up some of the documentation in an attempt to get things accurate with versioned releases.
163+
164+
## 4.2.0
165+
166+
### Breaking change
167+
168+
The method signature for `toGraphQLOutputType` and `toGraphQLInputType` have been changed to the following:
169+
170+
```php
171+
/**
172+
* @param \ReflectionMethod|\ReflectionProperty $reflector
173+
*/
174+
public function toGraphQLOutputType(Type $type, ?OutputType $subType, $reflector, DocBlock $docBlockObj): OutputType;
175+
176+
/**
177+
* @param \ReflectionMethod|\ReflectionProperty $reflector
178+
*/
179+
public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, $reflector, DocBlock $docBlockObj): InputType;
180+
```
181+
182+
### New features
183+
184+
- [@Input](attributes-reference.md#input) annotation is introduced as an alternative to `#[Factory]`. Now GraphQL input type can be created in the same manner as `#[Type]` in combination with `#[Field]` - [example](input-types.mdx#input-attribute).
185+
- New attributes has been added to [@Field](attributes-reference.md#field) annotation: `for`, `inputType` and `description`.
186+
- The following annotations now can be applied to class properties directly: `#[Field]`, `#[Logged]`, `#[Right]`, `@FailWith`, `@HideIfUnauthorized` and `#[Security]`.
187+
188+
## 4.1.0
189+
190+
### Breaking change
191+
192+
There is one breaking change introduced in the minor version (this was important to allow PHP 8 compatibility).
193+
194+
- The **ecodev/graphql-upload** package (used to get support for file uploads in GraphQL input types) is now a "recommended" dependency only.
195+
If you are using GraphQL file uploads, you need to add `ecodev/graphql-upload` to your `composer.json`.
196+
197+
### New features
198+
199+
- All annotations can now be accessed as PHP 8 attributes
200+
- The `@deprecated` annotation in your PHP code translates into deprecated fields in your GraphQL schema
201+
- You can now specify the GraphQL name of the Enum types you define
202+
- Added the possibility to inject pure Webonyx objects in GraphQLite schema
203+
204+
### Minor changes
205+
206+
- Migrated from `zend/diactoros` to `laminas/diactoros`
207+
- Making the annotation cache directory configurable
208+
209+
### Miscellaneous
210+
211+
- Migrated from Travis to Github actions
212+
213+
## 4.0.0
214+
215+
This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely
216+
changed.
217+
218+
### New features
219+
220+
- You can directly [annotate a PHP interface with `#[Type]` to make it a GraphQL interface](inheritance-interfaces.mdx#mapping-interfaces)
221+
- You can autowire services in resolvers, thanks to the new `@Autowire` annotation
222+
- Added [user input validation](validation.mdx) (using the Symfony Validator or the Laravel validator or a custom `#[Assertion]` annotation
223+
- Improved security handling:
224+
- Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)
225+
- Added fine-grained security using the `#[Security]` annotation. A field can now be [marked accessible or not depending on the context](fine-grained-security.mdx).
226+
For instance, you can restrict access to the field "viewsCount" of the type `BlogPost` only for post that the current user wrote.
227+
- You can now inject the current logged user in any query / mutation / field using the `#[InjectUser]` annotation
228+
- Performance:
229+
- You can inject the [Webonyx query plan in a parameter from a resolver](query-plan.mdx)
230+
- You can use the [dataloader pattern to improve performance drastically via the "prefetchMethod" attribute](prefetch-method.mdx)
231+
- Customizable error handling has been added:
232+
- You can throw [many errors in one exception](error-handling.mdx#many-errors-for-one-exception) with `TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException`
233+
- You can force input types using `@UseInputType(for="$id", inputType="ID!")`
234+
- You can extend an input types (just like you could extend an output type in v3) using [the new `#[Decorate]` annotation](extend-input-type.mdx)
235+
- In a factory, you can [exclude some optional parameters from the GraphQL schema](input-types#ignoring-some-parameters)
236+
237+
Many extension points have been added
238+
239+
- Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)
240+
- Added ["field middlewares"](field-middlewares.md) (useful to add middleware that modify the way GraphQL fields are handled)
241+
- Added a ["parameter type mapper"](argument-resolving.md) (useful to add customize parameter resolution or add custom annotations related to parameters)
242+
243+
New framework specific features:
244+
245+
### Symfony
246+
247+
- The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)
248+
249+
### Laravel
250+
251+
- [Native integration with the Laravel paginator](laravel-package-advanced.mdx#support-for-pagination) has been added
252+
253+
### Internals
254+
255+
- The `FieldsBuilder` class has been split in many different services (`FieldsBuilder`, `TypeHandler`, and a
256+
chain of *root type mappers*)
257+
- The `FieldsBuilderFactory` class has been completely removed.
258+
- Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points
259+
than 3.x. Try it out!
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
id: index
3+
title: GraphQLite
4+
slug: /
5+
sidebar_label: GraphQLite
6+
---
7+
8+
<p align="center">
9+
<img src="https://graphqlite.thecodingmachine.io/img/logo.svg" alt="GraphQLite logo" width="250" height="250" />
10+
</p>
11+
12+
13+
A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.
14+
15+
## Features
16+
17+
* Create a complete GraphQL API by simply annotating your PHP classes
18+
* Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!
19+
* Comes with batteries included: queries, mutations, subscriptions, mapping of arrays / iterators,
20+
file uploads, security, validation, extendable types and more!
21+
22+
## Basic example
23+
24+
First, declare a query in your controller:
25+
26+
```php
27+
class ProductController
28+
{
29+
#[Query]
30+
public function product(string $id): Product
31+
{
32+
// Some code that looks for a product and returns it.
33+
}
34+
}
35+
```
36+
37+
Then, annotate the `Product` class to declare what fields are exposed to the GraphQL API:
38+
39+
```php
40+
#[Type]
41+
class Product
42+
{
43+
#[Field]
44+
public function getName(): string
45+
{
46+
return $this->name;
47+
}
48+
// ...
49+
}
50+
```
51+
52+
That's it, you're good to go! Query and enjoy!
53+
54+
```graphql
55+
{
56+
product(id: 42) {
57+
name
58+
}
59+
}
60+
```

0 commit comments

Comments
 (0)