Commit 9175dc2
authored
* feat: explicit description attribute + SchemaFactory docblock toggle
Adds explicit `description` argument to every schema-defining attribute
(Query, Mutation, Subscription, Type, ExtendType, Factory) and a
SchemaFactory toggle to disable the docblock-as-description fallback.
Addresses #453 and the type-level portion of #740.
Behaviour highlights:
- Explicit attribute description always wins; `''` deliberately blocks
the docblock fallback; `null` falls through when the toggle is on.
- `setDocblockDescriptionsEnabled(false)` suppresses every docblock
fallback path so internal developer docblocks stop leaking to public
schema consumers.
- Duplicate descriptions across `#[Type]` + `#[ExtendType]` (or multiple
`#[ExtendType]`s on the same class) now throw a
DuplicateDescriptionOnTypeException naming every offending source.
- `InputTypeGenerator::mapFactoryMethod()` closes the long-standing
`// TODO: add comment argument.` — factory-produced input types now
participate in description resolution.
Consistency renames (breaking):
- `QueryFieldDescriptor`/`InputFieldDescriptor` `$comment` -> `$description`
and paired method renames (getDescription/withDescription/
withAddedDescriptionLines).
- `AbstractRequest` -> `AbstractGraphQLElement`
and `AnnotationReader::getRequestAnnotation()` ->
`getGraphQLElementAnnotation()`. The new name reflects the class's
role as the shared base for GraphQL schema-element attributes.
Tests: +26 new (resolver precedence matrix, per-attribute integration,
conflict exception, docblock-off regression). Full suite 528 passing
across cs-check, phpstan, and phpunit.
* feat: #[EnumValue] attribute for per-case enum metadata
Closes the deeper portion of #740 that the original PR left as future work:
native PHP 8.1+ enum cases can now carry an explicit GraphQL description
and deprecation reason without relying on docblock parsing.
```php
#[Type]
enum Genre: string
{
#[EnumValue(description: 'Fiction works including novels and short stories.')]
case Fiction = 'fiction';
#[EnumValue(deprecationReason: 'Use Fiction::Verse instead.')]
case Poetry = 'poetry';
}
```
Naming: follows the GraphQL specification's term ("enum values", §3.5.2,
`__EnumValue`, `enumValues`) rather than the PHP language term "case".
This matches every other graphqlite attribute (`Type`, `Field`, `Query`,
`ExtendType`, …) which mirrors GraphQL spec names, and webonyx/graphql-php's
internal `EnumValueDefinition`. Targets `Attribute::TARGET_CLASS_CONSTANT`
so it applies to PHP enum cases.
Precedence: explicit `description` / `deprecationReason` on the attribute
win over docblock summary / `@deprecated` tag. Passing `''` deliberately
suppresses the fallback at that site, matching every other `description`
argument across the library. No-attribute cases continue to fall back to
docblock — BC preserved.
Adds `AnnotationReader::getEnumValueAnnotation(ReflectionEnumUnitCase)`
helper and threads it through `EnumTypeMapper::mapByClassName()` where
enum case metadata is aggregated.
Tests: 5 new unit tests (attribute defaults / description / deprecation
reason / both / empty string) plus 4 new integration tests over a new
fixture enum (attribute-supplied description, docblock fallback, explicit
deprecation, toggle-off suppresses docblock-only case). Full suite
537/537 green across cs-check, phpstan, and phpunit.
* feat: deprecation notice for enum cases missing #[EnumValue]
Announces the upcoming opt-in migration for PHP enums mapped to GraphQL
enum types. Today every case is automatically exposed; a future major
release will require #[EnumValue] on each case that should participate
in the schema, matching the opt-in model #[Field] already uses for
methods and properties on object types.
The practical win is selective exposure: an internal enum case can opt
out of the public schema simply by omitting #[EnumValue], instead of
forcing schema authors to split an enum or rename cases.
Runtime behaviour is unchanged. When an enum annotated with #[Type]
exposes any case without an #[EnumValue] attribute, EnumTypeMapper
emits an E_USER_DEPRECATED notice that names the specific cases so the
migration path is mechanical — matching graphqlite's existing
deprecation pattern (e.g. addControllerNamespace, setGlobTTL).
Tests: +1 explicit deprecation assertion (using PHPUnit 11's
expectUserDeprecationMessageMatches). Existing integration tests that
exercise the Genre fixture surface the notice via PHPUnit's deprecation
reporting, confirming the warning fires in realistic scenarios. Full
suite 538/538 green across cs-check, phpstan, and phpunit; 4 intentional
deprecation events reported.
Docs: descriptions.md gains a "Future migration" subsection describing
the planned opt-in model and the current advisory notice.
* docs: clarify runtime behaviour for unmapped enum cases after future flip
The advisory deprecation already announces the opt-in migration. Add a
second paragraph spelling out what happens if a resolver returns a case
that lacks #[EnumValue] after the default flips: webonyx/graphql-php's
native enum serialization rejects it, the same spec-compliant behaviour
that applies to any unknown enum value. That is the mechanism that makes
selective exposure safe — internal cases cannot leak via a resolver —
and clarifies that omitting #[EnumValue] is a deliberate 'do not expose
this value' signal, not an oversight to work around.
* fix: narrow enum advisory to fire only when zero cases carry #[EnumValue]
Partial annotation is the mechanism that will hide internal cases from
the public schema once the default flips to opt-in; leaving some cases
unannotated is deliberate and must not produce a warning. Under the
previous logic the advisory fired for every partial annotation, which
would punish exactly the pattern the migration encourages.
New rule: fire the E_USER_DEPRECATED advisory only when a #[Type]-mapped
enum declares zero #[EnumValue] attributes across all its cases — the
signal that the developer has not yet engaged with the opt-in model.
Annotating even a single case acknowledges the migration and silences
the notice; from that point any combination of annotated and unannotated
cases is correct and intentional.
Implementation: EnumTypeMapper::mapByClassName() now tracks a single
`sawAnyEnumValueAttribute` flag while iterating cases and emits the
notice only when the flag stays false. Message rewritten to explain the
"at least one case" requirement and point the reader at the intended
migration path — including the fact that a bare #[EnumValue] on a
single case is a valid acknowledgement.
Tests:
- Replaces the previous "any case missing" assertion with
testEnumWithZeroEnumValueAttributesTriggersDeprecation using a new
DescriptionLegacyEnum/Era fixture that declares no #[EnumValue] at
all.
- Adds testEnumWithPartialEnumValueAttributesIsSilent to lock in the
partial-annotation contract against regressions.
- Pre-existing Color/Size/Position fixtures gain a bare #[EnumValue] on
their first case so their integration tests stop triggering the
advisory — demonstrates the minimal upgrade path recommended in the
docs.
Full suite 539/539 green across cs-check, phpstan, and phpunit, with
exactly 1 intentional deprecation coming from the dedicated advisory
test. The docs in descriptions.md are updated to reflect the corrected
semantics — "partial annotation is intentional and silent".
* docs: trim the enum migration subsection — short-lived transitional content
* refactor: sawAnyEnumValueAttribute -> hasEnumValueAttribute
Matches PHP's isser/haser naming convention and reads as a present-state
question instead of imperative history. Also aligns the private helper
name with the boolean semantics (plural -> singular since the check is
'has at least one attribute anywhere on the enum').
* docs: drop the 'bare single #[EnumValue] to silence' shortcut recommendation
Recommending users annotate a single case to silence the advisory is
actively misleading: once the default flips, every other case would
disappear from the schema — the exact opposite of what a user reaching
for the shortcut wanted. The honest recommendation is 'annotate every
case you want to keep exposed', which aligns their intent with the
post-flip behaviour.
- Warning message rewritten around 'add to every case you want exposed,
omit only from cases you want hidden' instead of 'at least one case
silences this notice'.
- descriptions.md migration subsection mirrors the same framing.
- Color/Size/Position test fixtures now annotate every case, not just
the first one, so they demonstrate the correct migration pattern
rather than the misleading shortcut.
1 parent a5f7549 commit 9175dc2
57 files changed
Lines changed: 1642 additions & 191 deletions
File tree
- src
- Annotations
- Exceptions
- Mappers
- Parameters
- Root
- Middlewares
- Types
- Utils
- tests
- Annotations
- Fixtures
- DescriptionDuplicate
- DescriptionLegacyEnum
- Description
- Integration/Models
- Integration
- Middlewares
- Utils
- website
- docs
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
11 | | - | |
| 12 | + | |
12 | 13 | | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
| |||
195 | 197 | | |
196 | 198 | | |
197 | 199 | | |
198 | | - | |
199 | | - | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
200 | 220 | | |
201 | 221 | | |
202 | | - | |
| 222 | + | |
203 | 223 | | |
204 | 224 | | |
205 | 225 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
Lines changed: 38 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
| 40 | + | |
38 | 41 | | |
39 | 42 | | |
40 | 43 | | |
| |||
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
58 | 74 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | | - | |
21 | | - | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| 30 | + | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
| |||
44 | 50 | | |
45 | 51 | | |
46 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
47 | 66 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
0 commit comments