uxf / graphql
    3.71.1
    2025-10-09 13:55 UTC
Requires
- php: ^8.3
- phpdocumentor/reflection-docblock: ^5.6
- uxf/core: 3.71.0
- webonyx/graphql-php: ^15.21
Conflicts
- uxf/gql: *
- 3.x-dev
- 3.71.1
- 3.71.0
- 3.70.5
- 3.70.4
- 3.70.3
- 3.70.2
- 3.70.0
- 3.69.12
- 3.69.7
- 3.69.6
- 3.69.5
- 3.69.3
- 3.69.2
- 3.69.1
- 3.69.0
- 3.68.0
- 3.67.3
- 3.67.0
- 3.66.0
- 3.65.4
- 3.65.3
- 3.65.2
- 3.65.0
- 3.64.4
- 3.64.3
- 3.64.2
- 3.64.1
- 3.64.0
- 3.63.1
- 3.62.4
- 3.62.3
- 3.62.2
- 3.62.1
- 3.62.0
- 3.61.7
- 3.61.6
- 3.61.5
- 3.61.4
- 3.61.1
- 3.61.0
- 3.60.12
- 3.60.11
- 3.60.10
- 3.60.9
- 3.60.8
- 3.60.7
- 3.60.6
- 3.60.5
- 3.60.4
- 3.60.3
- 3.60.2
- 3.60.1
- 3.60.0
This package is auto-updated.
Last update: 2025-10-09 12:00:56 UTC
README
Install
$ composer req uxf/graphql
Config
// config/packages/uxf.php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('uxf_graphql', [
        'destination' => __DIR__ . '/../generated/schema.graphql', // or array
        'fake' => [
            'enabled' => true, // default false
            'default_strategy' => true, // default false
            'namespace' => 'App\\Tests\\TInput', // optional
            'destination' => __DIR__ . '/../generated/fake', // optional
        ],
        'sources' => [
            __DIR__ . '/../src',
        ],
        'injected' => [
            // class => resolver
            Coyote::class => CoyoteResolver::class,
        ],
        'modifiers' => [
            AppTypeMapModifier::class,
        ],
        'hydrator_options' => [
            'allow_trim_string' => false, // default true
            'nullable_optional' => false, // default true
        ],
        'register_psr_17' => false, // default true
        'debug_flag' => DebugFlag::INCLUDE_DEBUG_MESSAGE, // default
    ]);
};
Query
use UXF\GraphQL\Attribute\Query;
final readonly class ArticlesQuery
{
    public function __construct(
        private ArticleProvider $articleProvider
    ) {
    }
    /**
     * @return ArticleType[]
     */
    #[Query('articles')]
    public function __invoke(int $limit, int $offset): array
    {
        return $this->articleProvider->list($limit, $offset);
    }
}
query Articles($limit: Int!, $offset: Int!) {
    articles(limit: $limit, offset: $offset) {
        id
        name
    }
}
Mutation
use UXF\GraphQL\Attribute\Mutation;
final readonly class ArticleCreateMutation
{
    public function __construct(
        private ArticleCreator $articleCreator,
        private ArticleProvider $articleProvider,
    ) {
    }
    #[Mutation('articleCreate')]
    public function __invoke(ArticleInput $input): ArticleType
    {
        $article = $this->articleCreator->create($input);
        return $this->articleProvider->get($article);
    }
}
mutation ArticleCreate($input: ArticleInput!) {
    articleCreate(input: $input) {
        id
        name
    }
}
Type
use UXF\GraphQL\Attribute\Type;
#[Type('Article')]
final readonly class ArticleType
{
    public function __construct(
        private Article $article,
        public int $id,
        public string $name,
    ) {
    }
    /**
     * @return TagType[]
     */
    public function tags(): array
    {
        return $this->article->getTags()->map(TagType::create(...))->getValues();
    }
}
type Article {
    id: Int
    name: String
    tags: [Tag!]!
}
Input
use UXF\GraphQL\Attribute\Input;
#[Input('ArticleInput')]
final readonly class ArticleInput
{
    /**
     * @param int[] $tags
     */
    public function __construct(
        public string $name,
        public ?Date $published,
        public array $tags,
        public int $score = 1,
    ) {
    }
}
input ArticleInput {
    name: String!
    published: Date
    tags: [Int!]!
    score: Int = 1
}
Limitations
⚠️ Nullable array items are not supported in inputs for now! (Supported only in output types + args)
#[Input]
final readonly class InvalidInput
{
    /**
     * @param (int|null)[] $a       -> does not work !!!
     * @param (int|null)[]|null $b  -> does not work !!!
     */
    public function __construct(
        public array $a,
        public ?array $b,
    ) {
    }
}
Validation
use Symfony\Component\Validator\Constraints as Assert;
use UXF\GraphQL\Attribute\Input;
#[Input]
final readonly class ValidationInput
{
    public function __construct(
        #[Assert\NotBlank] public string $string,
        #[Assert\Positive] public int $number,
    ) {
    }
}
NotSet
use UXF\Core\Http\Request\NotSet;
use UXF\GraphQL\Attribute\Input;
#[Input]
final readonly class PatchInput
{
    /**
     * @param int[]|null|NotSet $intArray
     */
    public function __construct(
        public string|null|NotSet $string = new NotSet(),
        public array|null|NotSet $intArray = new NotSet(),
    ) {
    }
}
input PatchInput {
  string: String
  intArray: [Int!]
}
@oneOf directive
use Ramsey\Uuid\UuidInterface;
use UXF\GraphQL\Attribute\Input;
#[Input(isOneOf: true)]
final readonly class OneOfInput
{
    public function __construct(
        public ?int $id,
        public ?UuidInterface $uuid,
    ) {
    }
}
input OneOfInput @oneOf {
  id: Int
  uuid: UUID
}
Generate fake input
use App\Entity\Donald;
use UXF\Core\Attribute\Entity;
use UXF\GraphQL\Attribute\Input;
#[Input(generateFake: true)]
final readonly class DoctrineInput
{
    /**
     * @param Donald[] $donalds
     */
    public function __construct(
        #[Entity] public Donald $donald,
        #[Entity] public array $donalds,
    ) {
    }
}
with
    $containerConfigurator->extension('uxf_graphql', [
        'fake' => [
            'enabled' => true,
            'default_strategy' => false,
            'namespace' => 'App\\Tests\\FakeInput',
            'destination' => __DIR__ . '/../tests/FakeInput',
        ],
        ...
    ]);
generates
namespace App\Tests\FakeInput;
final readonly class FakeDoctrineInput
{
    /**
     * @param int[] $donalds
     */
    public function __construct(
        public int $donald,
        public array $donalds,
    ) {
    }
}
Entity
Arguments
use App\Entity\Donald;
use UXF\Core\Attribute\Entity;
use UXF\GraphQL\Attribute\Query;
final readonly class DonaldQuery
{
    /**
     * @param Donald[] $donalds
     */
    #[Query('donald')]
    public function __invoke(
        #[Entity] Donald $donald,
        #[Entity('uuid')] Donald $donald2,
        #[Entity] array $donalds,
    ): DonaldType {
        ...
    }
}
query Donald($donald: Int!, $donald2: UUID!, $donalds: [Int!]!) {
    donald(
        donald: $donald
        donald2: $donald2
        donalds: $donalds
    ) {
        id
        name
    }
}
Input
use App\Entity\Donald;
use UXF\Core\Attribute\Entity;
use UXF\GraphQL\Attribute\Input;
#[Input]
final readonly class DoctrineInput
{
    /**
     * @param Donald[] $donalds
     * @param Donald[] $donaldNames
     * @param Donald[] $donaldUuids
     * @param Donald[] $donaldRefs
     * @param Donald[]|null $donaldsNullable
     * @param Donald[]|null $donaldsNullableOptional
     * @param Donald[] $donaldsOptional
     */
    public function __construct(
        #[Entity] public Donald $donald,
        #[Entity('name')] public Donald $donaldName,
        #[Entity('uuid')] public Donald $donaldUuid,
        #[Entity('minnie')] public Donald $donaldRef,
        #[Entity('enum')] public Donald $donaldEnum,
        #[Entity('enumInt')] public Donald $donaldEnumInt,
        #[Entity] public array $donalds,
        #[Entity('name')] public array $donaldNames,
        #[Entity('uuid')] public array $donaldUuids,
        #[Entity('minnie')] public array $donaldRefs,
        #[Entity('name')] public ?Donald $donaldNullable,
        #[Entity('uuid')] public ?array $donaldsNullable,
        #[Entity('name')] public ?Donald $donaldNullableOptional = null,
        #[Entity('name')] public ?array $donaldsNullableOptional = null,
        #[Entity('name')] public array $donaldsOptional = [],
    ) {
    }
}
input DoctrineInput {
  donald: Int!
  donaldName: String!
  donaldUuid: UUID!
  donaldRef: Int!
  donaldEnum: PlutoEnum!
  donaldEnumInt: GoofyEnum!
  donalds: [Int!]!
  donaldNames: [String!]!
  donaldUuids: [UUID!]!
  donaldRefs: [Int!]!
  donaldNullable: String
  donaldsNullable: [UUID!]
  donaldNullableOptional: String = null
  donaldsNullableOptional: [String!] = null
  donaldsOptional: [String!]! = []
}
Union
Property
use UXF\GraphQL\Attribute\Type;
use UXF\GraphQL\Attribute\Union;
#[Type('Bag')]
final readonly class BagType
{
    public function __construct(
        #[Union('Fruit')] public Apple|Banana $item,
    ) {
    }
}
union Fruit = Apple | Banana
type Bag {
    item: Fruit!
}
Method
use UXF\GraphQL\Attribute\Type;
use UXF\GraphQL\Attribute\Union;
#[Type('Bag')]
final readonly class BagType
{
    /**
     * @return (Apple|Banana)[]
     */
    #[Union('Fruit')]
    public function getItems(): array
    {
        return [new Banana(2, 'C')];
    }
}
union Fruit = Apple | Banana
type Bag {
    items: [Fruit!]!
}
Inject
- Symfony Request injector is registered by default
Query/Mutation
use UXF\GraphQL\Attribute\Inject;
final readonly class PlutoQuery
{
    #[Query(name: 'pluto')]
    public function __invoke(#[Inject] Coyote $coyote): Pluto
    {
        ...
    }
}
Type method
use UXF\GraphQL\Attribute\Inject;
#[Type('Pluto')]
final readonly class PlutoQuery
{
    /**
     * @return string[]
     */
    public function getItems(#[Inject] Coyote $coyote): array
    {
        ...
    }
}
Injector service
use Symfony\Component\HttpFoundation\Request;
use UXF\GraphQL\Service\Injector\InjectedArgument;
final readonly class CoyoteInjector
{
    public function __invoke(Request $request, InjectedArgument $argument): Coyote
    {
        assert($argument->type === Coyote::class);
        return new Coyote();
    }
}
Autowire
use UXF\GraphQL\Attribute\Autowire;
#[Type('Pluto')]
final readonly class PlutoQuery
{
    /**
     * @return string[]
     */
    public function getItems(#[Autowire] ResponseProvider $provider): array
    {
        ...
    }
}
type Pluto {
    items: [String!]!
}
Field
Input
use UXF\GraphQL\Attribute\Field;
#[Input]
final readonly class PlutoInput
{
    public function __construct(
        #[Field(inputType: 'Long')] public int $long,
    ) {
    }
}
input PlutoInput {
    long: Long!
}
Output
use UXF\GraphQL\Attribute\Field;
#[Type]
final readonly class Pluto
{
    public function __construct(
        public string $string,
        #[Field(outputType: 'Long')] public int $long,
    ) {
    }
    #[Field(outputType: 'Long')]
    public function getLong2(): int
    {
        return -1;
    }
}
type Pluto {
    string: String!
    long: Long!
    long2: Long!
}
Deprecated
Supported attributes:
- #[Deprecated]
- #[JetBrains\PhpStorm\Deprecated]
use Deprecated;
use JetBrains\PhpStorm\Deprecated as JBDeprecated;
use UXF\GraphQL\Attribute\Query;
final readonly class XQuery
{
    #[Query('x')]
    #[Deprecated('Query')]
    public function __invoke(#[JBDeprecated] ?XInput $input): XType
    {
        return new XType('x');
    }
}
#[Input]
final readonly class XInput
{
    public function __construct(
        #[Deprecated('Input field')] public ?string $string,
    ) {
    }
}
#[Type('X')]
final readonly class XType
{
    public function __construct(
        #[JBDeprecated] public string $string,
    ) {
    }
    #[Deprecated('Method field')]
    public function getInt(): int
    {
        return 0;
    }
    #[Union('Union')]
    #[Deprecated]
    public function getUnion(): A|B
    {
        return new A('');
    }
}
Ignore
use UXF\GraphQL\Attribute\Ignore;
#[Type]
final readonly class Pluto
{
    public function __construct(
        public string $string,
        #[Ignore] public bool $ignoredProperty,
    ) {
    }
    #[Ignore]
    public function ignored(): bool
    {
        return true;
    }
}
type Pluto {
    string: String!
}
SchemaModifier
// config/packages/uxf.php
return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('uxf_graphql', [
        'modifiers' => [
            AppTypeMapModifier::class,
        ],
        ...
    ]);
};
use UXF\GraphQL\Plugin\SchemaModifier;
final class AppSchemaModifier implements SchemaModifier
{
    // add custom types
    public static function modifyTypeMap(TypeMap $typeMap): void
    {
        // simple scalar
        $typeMap->scalars[ResultTime::class] = new ScalarTypeSchema(
            name: 'ResultTime',
            phpType: ResultTime::class,
            definition: new ScalarDefinition(ResultTimeType::class),
        );
        // scalar with dependency
        $typeMap->scalars[SuperScalar::class] = new ScalarTypeSchema(
            name: 'SuperScalar',
            phpType: SuperScalar::class,
            definition: new ScalarDefinition(SuperScalarType::class, 'int'),
        );
    }
    // modify input parse function
    public static function modifyParseValueFn(InputTypeSchema $inputType): ?string
    {
        if ($inputType->phpType === Money::class) {
            return 'function (array $values) {
    try {
        return \\' . Money::class . "::of(\$values['amount'], \$values['currency']);
    } catch (\Exception) {
        throw new Error('Invalid money format', previous: new UserError('Invalid money format'));
    }
}";
        }
        return null;
    }
    public static function getDefaultPriority(): int
    {
        return 10;
    }
}