uxf / graphql
3.61.4
2025-02-22 22:50 UTC
Requires
- php: ^8.3
- phpdocumentor/reflection-docblock: ^5.6
- uxf/core: 3.61.1
- webonyx/graphql-php: ^15.19
Conflicts
- uxf/gql: *
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
}
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!]
}
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!
}
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;
}
}