Skip to content

Add code-first applied directives support for schema printing#1857

Draft
spawnia wants to merge 14 commits into
masterfrom
code-first-directives
Draft

Add code-first applied directives support for schema printing#1857
spawnia wants to merge 14 commits into
masterfrom
code-first-directives

Conversation

@spawnia

@spawnia spawnia commented Feb 14, 2026

Copy link
Copy Markdown
Collaborator

Add applied directives support in code-first type/config definitions using DirectiveNode, schema-level schemaDirectives config support, and applied-directive printing behind SchemaPrinter doPrint option includeAppliedDirectives=true.

Refs #588

Comment thread src/Type/SchemaConfig.php
* subscription?: MaybeLazyObjectType,
* types?: Types|null,
* directives?: array<Directive>|null,
* schemaDirectives?: array<DirectiveNode>|null,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I like the naming of that one, but we can not simply call it directives like in other schema elements - as that is already taken.

Comment on lines +60 to +61
* @phpstan-type AppliedDirectiveDefinition Schema|Argument|FieldDefinition|InputObjectField|EnumValueDefinition|ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType
* @phpstan-type AppliedDirectiveAstNode SchemaDefinitionNode|SchemaExtensionNode|InputValueDefinitionNode|FieldDefinitionNode|EnumValueDefinitionNode|ScalarTypeDefinitionNode|ScalarTypeExtensionNode|ObjectTypeDefinitionNode|ObjectTypeExtensionNode|InterfaceTypeDefinitionNode|InterfaceTypeExtensionNode|UnionTypeDefinitionNode|UnionTypeExtensionNode|EnumTypeDefinitionNode|EnumTypeExtensionNode|InputObjectTypeDefinitionNode|InputObjectTypeExtensionNode

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those types could probably be moved to somewhere else and imported here.

*/
private static function collectAppliedDirectives(object $definition, array $excludedDirectiveNames = []): array
{
$directives = [];

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the pass-by-reference API of filling this, might redesign this to be functional.

Comment on lines +707 to +709
if (! in_array($directive->name->value, $excludedDirectiveNames, true)) {
$directives[] = $directive;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated with above, might extract.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs tests/specification for conflicts between built-in directives (e.g. deprecationReason + manual @deprecated addition).

* sortFields?: bool,
* sortInputFields?: bool,
* sortTypes?: bool,
* includeAppliedDirectives?: bool,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option might not be fine-grained enough. Printing extra directives is not necessarily all-or-nothing - depending on the use case (e.g. federation), we might only need to print selected directives. I think that selection is a function of the schema printer - otherwise, we require different schema constructions.

@spawnia

spawnia commented Feb 15, 2026

Copy link
Copy Markdown
Collaborator Author

@ruudk @simPod @LastDragon-ru First draft, what do you think? I have added some concrete discussion points. Feel free to comment and/or suggest anything else.

@LastDragon-ru

Copy link
Copy Markdown
Contributor
/** @var array<GraphQL\Language\AST\DirectiveNode> */
public array $directives;

I'm not sure about suggested GraphQL\Language\AST\DirectiveNode because seems the only way to create it is Parser::directive("@directive(arg: 123)") (+ requires args escaping...).

If I understand correctly, now we have only \GraphQL\Type\Definition\Directive - the definition of the directive (and example in the documentation). So maybe a new type for "usage" will be better? (but we will also need a way to get the definition)

@spawnia

spawnia commented Feb 15, 2026

Copy link
Copy Markdown
Collaborator Author

Responding to #1857 (comment) by @LastDragon-ru:

I'm not sure about suggested GraphQL\Language\AST\DirectiveNode because seems the only way to create it is Parser::directive("@directive(arg: 123)") (+ requires args escaping...).

Direct instantiation is possible, albeit a bit cumbersome:

use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\NodeList;

$node = new DirectiveNode([
    'name' => new NameNode([
        'value' => 'directive',
    ]),
    'arguments' => new NodeList([
        new ArgumentNode([
            'name' => new NameNode([
                'value' => 'arg',
            ]),
            'value' => new IntValueNode([
                'value' => '123', // IntValueNode stores numeric literal as string
            ]),
        ]),
    ]),
]);

I guess this could be simplified through a dedicated type, but that makes it harder to handle directive applications in the printer, given we want to support printing from SDL-first and code-first schemas. A simplified version might look something like this:

$applied = new AppliedDirective([
    'name' => 'directive',
    'arguments' => [
        'arg' => 123,
    ],
]);

However, that runs into an issue of distinguishing between string and enum literals. 'arg' => 'FOO' might print as arg: "FOO" or arg: FOO, depending on the type of the directive argument. We could add an explicit marker, or require directive type information to print this correctly.

@LastDragon-ru

LastDragon-ru commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

However, that runs into an issue of distinguishing between string and enum literals

Good point. Another point - args: array[] is not strict, so we probably will need some checks to make phpstan happy :) I just personally prefer to avoid mixed. Although directives args never properly validated even in graphal-js... (#1539 graphql/graphql-js#1389)

I guess this could be simplified through a dedicated type, but that makes it harder to handle directive applications in the printer, given we want to support printing from SDL-first and code-first schemas.

Yep. But the current approach also makes it harder. If we have astNode and directives what should be used?

  • astNode->directives
  • directives
  • both

One more question - will it ($directives) conflict with graphql/graphql-js#1343 ?

# Conflicts:
#	docs/class-reference.md
#	src/Type/Definition/CustomScalarType.php
#	src/Type/Definition/ScalarType.php
#	src/Utils/SchemaPrinter.php

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds code-first support for applied directives (as DirectiveNode instances) on schema/type/field/arg definitions, and enables optionally printing those applied directives in SDL via SchemaPrinter.

Changes:

  • Add SchemaConfig::$schemaDirectives (+ getters/setter) to support schema-level applied directives in code-first schemas.
  • Add directives config/property support (as DirectiveNode[]) to several definition classes (types, fields, args, enum values, input fields).
  • Add SchemaPrinter option includeAppliedDirectives to print applied directives collected from config + AST nodes + extension AST nodes; add tests/docs.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Utils/SchemaPrinterTest.php Adds coverage for applied-directive printing (off by default, on when enabled, includes AST + extensions).
tests/Type/SchemaConfigTest.php Adds coverage for SchemaConfig::schemaDirectives getter/setter behavior.
src/Utils/SchemaPrinter.php Implements applied-directive collection/printing behind includeAppliedDirectives.
src/Type/SchemaConfig.php Introduces schemaDirectives storage and API on schema config.
src/Type/Definition/UnionType.php Adds directives config/property for unions.
src/Type/Definition/ScalarType.php Adds directives config/property for scalars.
src/Type/Definition/ObjectType.php Adds directives config/property for objects.
src/Type/Definition/InterfaceType.php Adds directives config/property for interfaces.
src/Type/Definition/InputObjectType.php Adds directives config/property for input objects (and removes duplicated @throws).
src/Type/Definition/InputObjectField.php Adds directives config/property for input fields.
src/Type/Definition/FieldDefinition.php Adds directives config/property for field definitions.
src/Type/Definition/EnumValueDefinition.php Adds directives config/property for enum values.
src/Type/Definition/EnumType.php Adds directives config/property for enum types.
src/Type/Definition/CustomScalarType.php Updates config phpdoc to mention directives.
src/Type/Definition/Argument.php Adds directives config/property for arguments.
docs/class-reference.md Documents schemaDirectives and includeAppliedDirectives options/type aliases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 407 to +410
return static::printDescription($options, $type)
. "scalar {$type->name}"
. static::printSpecifiedBy($type);
. static::printSpecifiedBy($type)
. self::printAppliedDirectivesIfEnabled($options, $type);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants