├── phpstan.neon ├── src ├── Request │ ├── RequestFactory.php │ ├── Exception │ │ ├── InvalidMethod.php │ │ ├── QueryMissing.php │ │ ├── QueryNotString.php │ │ ├── VariablesNotObject.php │ │ ├── InvalidMultipartRequest.php │ │ ├── UnknownKey.php │ │ ├── OperationNameNotString.php │ │ └── RequestError.php │ ├── Request.php │ ├── PsrRequestFactory.php │ └── JsonRequestFactory.php ├── Typesystem │ ├── Contract │ │ ├── ExecutableDirective.php │ │ ├── TypeSystemDirective.php │ │ ├── LeafType.php │ │ ├── TypeConditionable.php │ │ ├── Type.php │ │ ├── Entity.php │ │ ├── Component.php │ │ ├── ModifierType.php │ │ ├── AbstractTypeVisitor.php │ │ ├── TypeVisitor.php │ │ ├── EntityVisitor.php │ │ ├── AbstractType.php │ │ ├── Directive.php │ │ ├── InterfaceImplementor.php │ │ ├── NamedTypeVisitor.php │ │ ├── ComponentVisitor.php │ │ └── NamedType.php │ ├── Location │ │ ├── SelectionDirectiveResult.php │ │ ├── EnumLocation.php │ │ ├── UnionLocation.php │ │ ├── EnumItemLocation.php │ │ ├── ScalarLocation.php │ │ ├── SchemaLocation.php │ │ ├── FragmentDefinitionLocation.php │ │ ├── FragmentSpreadLocation.php │ │ ├── InlineFragmentLocation.php │ │ ├── QueryLocation.php │ │ ├── MutationLocation.php │ │ ├── ExecutableDirectiveLocation.php │ │ ├── SubscriptionLocation.php │ │ ├── InputObjectLocation.php │ │ ├── ObjectLocation.php │ │ ├── TypeSystemDirectiveLocation.php │ │ ├── VariableDefinitionLocation.php │ │ ├── FieldLocation.php │ │ ├── ArgumentDefinitionLocation.php │ │ └── FieldDefinitionLocation.php │ ├── Exception │ │ ├── VarianceError.php │ │ ├── TypeError.php │ │ ├── TypeNamesNotUnique.php │ │ ├── DirectiveNamesNotUnique.php │ │ ├── DirectiveIncorrectType.php │ │ ├── OneOfInputInvalidFields.php │ │ ├── UnionTypeMustDefineOneOrMoreTypes.php │ │ ├── RootOperationTypesMustBeWithinContainer.php │ │ ├── InputTypeMustDefineOneOreMoreFields.php │ │ ├── InterfaceDirectivesNotPreserved.php │ │ ├── InterfaceOrTypeMustDefineOneOrMoreFields.php │ │ ├── RootOperationTypesMustBeDifferent.php │ │ ├── DuplicateNonRepeatableDirective.php │ │ ├── OneOfDirectiveNotSatisfied.php │ │ ├── EnumItemInvalid.php │ │ ├── FieldResolverVoidReturnType.php │ │ ├── FieldResolverNotIterable.php │ │ ├── FieldResolverNullabilityMismatch.php │ │ ├── InputCycleDetected.php │ │ ├── FieldInvalidTypeUsage.php │ │ ├── InterfaceCycleDetected.php │ │ ├── ArgumentInvalidTypeUsage.php │ │ ├── InterfaceContractMissingField.php │ │ ├── InterfaceContractFieldTypeMismatch.php │ │ ├── FieldDirectiveNotCovariant.php │ │ ├── InterfaceContractMissingArgument.php │ │ ├── InterfaceContractArgumentTypeMismatch.php │ │ ├── InterfaceContractNewArgumentWithoutDefault.php │ │ └── ArgumentDirectiveNotContravariant.php │ ├── Field │ │ ├── ResolvableFieldSet.php │ │ ├── FieldSet.php │ │ ├── ResolvableField.php │ │ └── Field.php │ ├── Utils │ │ ├── THasDirectives.php │ │ ├── THasDescription.php │ │ ├── TOptionalDescription.php │ │ ├── TInterfaceImplementor.php │ │ ├── TMetaFields.php │ │ └── TDeprecatable.php │ ├── DirectiveUsage │ │ ├── DirectiveUsageSet.php │ │ └── DirectiveUsage.php │ ├── NotNullType.php │ ├── Attribute │ │ └── Description.php │ ├── ListType.php │ ├── TypeSet.php │ ├── InterfaceSet.php │ ├── Spec │ │ ├── BooleanType.php │ │ ├── StringType.php │ │ ├── SpecifiedByDirective.php │ │ ├── IdType.php │ │ ├── FloatType.php │ │ ├── IntType.php │ │ ├── OneOfDirective.php │ │ ├── SkipDirective.php │ │ ├── IncludeDirective.php │ │ └── DeprecatedDirective.php │ ├── Introspection │ │ ├── TypeKind.php │ │ ├── DirectiveLocation.php │ │ ├── EnumValue.php │ │ ├── Directive.php │ │ ├── Schema.php │ │ └── InputValue.php │ ├── Argument │ │ ├── ArgumentSet.php │ │ └── Argument.php │ ├── EnumItem │ │ ├── EnumItemSet.php │ │ └── EnumItem.php │ ├── UnionType.php │ ├── Visitor │ │ ├── IsInputableVisitor.php │ │ ├── IsOutputableVisitor.php │ │ ├── TypeKindVisitor.php │ │ ├── GetShapingTypeVisitor.php │ │ ├── GetNamedTypeVisitor.php │ │ ├── PrintNameVisitor.php │ │ ├── IsImplementedByVisitor.php │ │ └── IsInstanceOfVisitor.php │ ├── InterfaceType.php │ ├── ScalarType.php │ ├── InputType.php │ ├── Schema.php │ └── EnumType.php ├── Normalizer │ ├── Refiner │ │ ├── Module │ │ │ ├── RefinerModule.php │ │ │ ├── EmptyFragmentModule.php │ │ │ ├── DuplicateFragmentSpreadModule.php │ │ │ └── DuplicateFieldModule.php │ │ └── SelectionSetRefiner.php │ ├── Selection │ │ ├── Selection.php │ │ ├── SelectionVisitor.php │ │ ├── InlineFragment.php │ │ ├── FragmentSpread.php │ │ ├── SelectionSet.php │ │ └── Field.php │ ├── Validator │ │ ├── Module │ │ │ ├── ValidatorModule.php │ │ │ └── FieldForName.php │ │ ├── SelectionSetValidator.php │ │ └── FragmentCycleValidator.php │ ├── Exception │ │ ├── FragmentCycle.php │ │ ├── SelectionOnLeaf.php │ │ ├── ConflictingFieldArguments.php │ │ ├── ConflictingFieldDirectives.php │ │ ├── SelectionOnComposite.php │ │ ├── VariableTypeMismatch.php │ │ ├── ConflictingFieldAlias.php │ │ ├── ConflictingFieldType.php │ │ ├── TypeConditionOutputable.php │ │ ├── SelectionOnUnion.php │ │ ├── VariableInConstContext.php │ │ ├── NormalizerError.php │ │ ├── UnknownType.php │ │ ├── UnknownDirective.php │ │ ├── UnknownVariable.php │ │ ├── UnknownArgument.php │ │ ├── DirectiveNotExecutable.php │ │ ├── DuplicatedDirective.php │ │ ├── UnknownFragment.php │ │ ├── VariableTypeInputable.php │ │ ├── UnknownField.php │ │ ├── DirectiveIncorrectLocation.php │ │ ├── DirectiveIncorrectUsage.php │ │ ├── InvalidFragmentType.php │ │ └── OperationNotSupported.php │ ├── FinalizedRequest.php │ ├── NormalizedRequest.php │ ├── VariableValueSet.php │ ├── Variable │ │ ├── VariableSet.php │ │ └── Variable.php │ ├── Directive │ │ ├── Directive.php │ │ └── DirectiveSet.php │ ├── Operation │ │ ├── OperationSet.php │ │ └── Operation.php │ ├── Visitor │ │ ├── ApplyVariablesVisitor.php │ │ └── GetFieldVisitor.php │ └── Finalizer.php ├── Resolver │ ├── Exception │ │ ├── ResolverError.php │ │ └── FieldResultTypeMismatch.php │ ├── ErrorHandlingMode.php │ ├── Result.php │ ├── ExceptionHandler.php │ └── Visitor │ │ └── ResolveVisitor.php ├── Value │ ├── Exception │ │ ├── ValueCannotBeNull.php │ │ ├── ValueCannotBeOmitted.php │ │ ├── ValueError.php │ │ └── InvalidValue.php │ ├── ResolvedValue.php │ ├── OutputValue.php │ ├── Value.php │ ├── ListResolvedValue.php │ ├── ListIntermediateValue.php │ ├── TypeIntermediateValue.php │ ├── ListValue.php │ ├── InputedValue.php │ ├── FieldValue.php │ ├── NullValue.php │ ├── TypeValue.php │ ├── ListInputedValue.php │ ├── ArgumentValue.php │ ├── ArgumentValueSet.php │ ├── VariableValue.php │ ├── ScalarValue.php │ ├── EnumValue.php │ └── Visitor │ │ └── CreateResolvedValueVisitor.php └── Module │ ├── ModuleSet.php │ └── Module.php ├── phpcs.xml ├── SECURITY.md ├── LICENSE └── composer.json /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | rememberPossiblyImpureFunctionValues: false 3 | checkMissingOverrideMethodAttribute: true 4 | -------------------------------------------------------------------------------- /src/Request/RequestFactory.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | src 4 | tests 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Normalizer/Exception/SelectionOnUnion.php: -------------------------------------------------------------------------------- 1 | value); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Normalizer/FinalizedRequest.php: -------------------------------------------------------------------------------- 1 | $visitor 13 | * @return T 14 | */ 15 | public function accept(TypeVisitor $visitor) : mixed; 16 | } 17 | -------------------------------------------------------------------------------- /src/Value/Exception/ValueCannotBeOmitted.php: -------------------------------------------------------------------------------- 1 | directiveUsages; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/OneOfDirectiveNotSatisfied.php: -------------------------------------------------------------------------------- 1 | visitNotNull($this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Normalizer/Exception/UnknownField.php: -------------------------------------------------------------------------------- 1 | variables[$offset]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/EnumItemInvalid.php: -------------------------------------------------------------------------------- 1 | value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/FieldResolverVoidReturnType.php: -------------------------------------------------------------------------------- 1 | $inputCycle 13 | */ 14 | public function __construct( 15 | array $inputCycle, 16 | ) 17 | { 18 | parent::__construct([\implode(' -> ', $inputCycle)]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Normalizer/Exception/OperationNotSupported.php: -------------------------------------------------------------------------------- 1 | value]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Typesystem/Location/FragmentSpreadLocation.php: -------------------------------------------------------------------------------- 1 | $interfaceCycle 13 | */ 14 | public function __construct( 15 | array $interfaceCycle, 16 | ) 17 | { 18 | parent::__construct([\implode(' -> ', $interfaceCycle)]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/ArgumentInvalidTypeUsage.php: -------------------------------------------------------------------------------- 1 | visitList($this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Typesystem/Location/ExecutableDirectiveLocation.php: -------------------------------------------------------------------------------- 1 | innerType; 20 | } 21 | 22 | public function list() : ListType 23 | { 24 | return new ListType($this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/InterfaceContractMissingField.php: -------------------------------------------------------------------------------- 1 | getAttributes(Description::class); 15 | 16 | if (\count($attrs) === 1) { 17 | return $attrs[0]->newInstance()->getValue(); 18 | } 19 | 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Value/Exception/ValueError.php: -------------------------------------------------------------------------------- 1 | outputable; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/AbstractTypeVisitor.php: -------------------------------------------------------------------------------- 1 | getName(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/TypeVisitor.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface TypeVisitor extends NamedTypeVisitor 15 | { 16 | /** 17 | * @return T 18 | */ 19 | public function visitNotNull(NotNullType $notNull) : mixed; 20 | 21 | /** 22 | * @return T 23 | */ 24 | public function visitList(ListType $list) : mixed; 25 | } 26 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/EntityVisitor.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface EntityVisitor extends NamedTypeVisitor 15 | { 16 | /** 17 | * @return T 18 | */ 19 | public function visitSchema(Schema $schema) : mixed; 20 | 21 | /** 22 | * @return T 23 | */ 24 | public function visitDirective(Directive $directive) : mixed; 25 | } 26 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/InterfaceContractFieldTypeMismatch.php: -------------------------------------------------------------------------------- 1 | name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Typesystem/Location/InputObjectLocation.php: -------------------------------------------------------------------------------- 1 | description; 17 | } 18 | 19 | public function setDescription(string $description) : self 20 | { 21 | $this->description = $description; 22 | 23 | return $this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/FieldDirectiveNotCovariant.php: -------------------------------------------------------------------------------- 1 | getName(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Typesystem/Location/TypeSystemDirectiveLocation.php: -------------------------------------------------------------------------------- 1 | arguments->applyVariables($variables); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Value/ListIntermediateValue.php: -------------------------------------------------------------------------------- 1 | rawValue; 22 | } 23 | 24 | #[\Override] 25 | public function getType() : ListType 26 | { 27 | return $this->type; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Normalizer/Directive/DirectiveSet.php: -------------------------------------------------------------------------------- 1 | applyVariables($variables); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/InterfaceContractArgumentTypeMismatch.php: -------------------------------------------------------------------------------- 1 | implements; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Normalizer/Operation/OperationSet.php: -------------------------------------------------------------------------------- 1 | name 24 | ?? ''; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Normalizer/Selection/InlineFragment.php: -------------------------------------------------------------------------------- 1 | visitInlineFragment($this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/AbstractType.php: -------------------------------------------------------------------------------- 1 | $visitor 15 | * @return T 16 | */ 17 | #[\Override] 18 | abstract public function accept(AbstractTypeVisitor $visitor) : mixed; 19 | 20 | abstract public function createResolvedValue(mixed $rawValue) : TypeIntermediateValue; 21 | } 22 | -------------------------------------------------------------------------------- /src/Typesystem/InterfaceSet.php: -------------------------------------------------------------------------------- 1 | getName(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Typesystem/Exception/ArgumentDirectiveNotContravariant.php: -------------------------------------------------------------------------------- 1 | visitFragmentSpread($this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Normalizer/Validator/SelectionSetValidator.php: -------------------------------------------------------------------------------- 1 | selections), 22 | ]; 23 | 24 | foreach ($modules as $module) { 25 | $module->validate(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Normalizer/Operation/Operation.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function getLocations() : array; 23 | 24 | public function getArguments() : ArgumentSet; 25 | } 26 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/BooleanType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Description('Boolean built-in type')] 14 | final class BooleanType extends ScalarType 15 | { 16 | protected const NAME = 'Boolean'; 17 | 18 | #[\Override] 19 | public function validateAndCoerceInput(mixed $rawValue) : ?bool 20 | { 21 | return \is_bool($rawValue) 22 | ? $rawValue 23 | : null; 24 | } 25 | 26 | #[\Override] 27 | public function coerceOutput(mixed $rawValue) : bool 28 | { 29 | return $rawValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/StringType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Description('String built-in type')] 14 | final class StringType extends ScalarType 15 | { 16 | protected const NAME = 'String'; 17 | 18 | #[\Override] 19 | public function validateAndCoerceInput(mixed $rawValue) : ?string 20 | { 21 | return \is_string($rawValue) 22 | ? $rawValue 23 | : null; 24 | } 25 | 26 | #[\Override] 27 | public function coerceOutput(mixed $rawValue) : string 28 | { 29 | return $rawValue; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Normalizer/Selection/SelectionSet.php: -------------------------------------------------------------------------------- 1 | accept($visitor); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/InterfaceImplementor.php: -------------------------------------------------------------------------------- 1 | accept(new IsInputableVisitor())) { 23 | throw new VariableTypeInputable($this->name); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Value/TypeIntermediateValue.php: -------------------------------------------------------------------------------- 1 | validateNonNullValue($rawValue)) { 18 | throw new InvalidValue($type, $rawValue, false); 19 | } 20 | } 21 | 22 | #[\Override] 23 | public function getRawValue(bool $forResolvers = false) : mixed 24 | { 25 | return $this->rawValue; 26 | } 27 | 28 | #[\Override] 29 | public function getType() : Type 30 | { 31 | return $this->type; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Typesystem/Field/ResolvableField.php: -------------------------------------------------------------------------------- 1 | resolveFn = $resolveFn; 21 | } 22 | 23 | #[\Override] 24 | public static function create(string $name, Type $type, ?callable $resolveFn = null) : self 25 | { 26 | return new self($name, $type, $resolveFn); 27 | } 28 | 29 | public function getResolveFunction() : \Closure 30 | { 31 | return $this->resolveFn; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Typesystem/Introspection/TypeKind.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface NamedTypeVisitor extends AbstractTypeVisitor 17 | { 18 | /** 19 | * @return T 20 | */ 21 | public function visitType(Type $type) : mixed; 22 | 23 | /** 24 | * @return T 25 | */ 26 | public function visitInput(InputType $input) : mixed; 27 | 28 | /** 29 | * @return T 30 | */ 31 | public function visitScalar(ScalarType $scalar) : mixed; 32 | 33 | /** 34 | * @return T 35 | */ 36 | public function visitEnum(EnumType $enum) : mixed; 37 | } 38 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/IdType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Description('ID built-in type')] 14 | final class IdType extends ScalarType 15 | { 16 | protected const NAME = 'ID'; 17 | 18 | #[\Override] 19 | public function validateAndCoerceInput(mixed $rawValue) : ?string 20 | { 21 | // coerce int to string 22 | $rawValue = \is_int($rawValue) 23 | ? (string) $rawValue 24 | : $rawValue; 25 | 26 | return \is_string($rawValue) 27 | ? $rawValue 28 | : null; 29 | } 30 | 31 | #[\Override] 32 | public function coerceOutput(mixed $rawValue) : string 33 | { 34 | return $rawValue; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Normalizer/Selection/Field.php: -------------------------------------------------------------------------------- 1 | field->getName(); 26 | } 27 | 28 | #[\Override] 29 | public function accept(SelectionVisitor $visitor) : mixed 30 | { 31 | return $visitor->visitField($this); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/FloatType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Description('Float built-in type')] 14 | final class FloatType extends ScalarType 15 | { 16 | protected const NAME = 'Float'; 17 | 18 | #[\Override] 19 | public function validateAndCoerceInput(mixed $rawValue) : ?float 20 | { 21 | // coerce int to float 22 | $rawValue = \is_int($rawValue) 23 | ? (float) $rawValue 24 | : $rawValue; 25 | 26 | return \is_float($rawValue) && \is_finite($rawValue) 27 | ? $rawValue 28 | : null; 29 | } 30 | 31 | #[\Override] 32 | public function coerceOutput(mixed $rawValue) : float 33 | { 34 | return $rawValue; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Resolver/Result.php: -------------------------------------------------------------------------------- 1 | data instanceof TypeValue) { 25 | $return->data = $this->data; 26 | } 27 | 28 | if (\is_array($this->errors)) { 29 | $return->errors = $this->errors; 30 | } 31 | 32 | return $return; 33 | } 34 | 35 | public function toString() : string 36 | { 37 | return Json::fromNative($this->jsonSerialize())->toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Normalizer/Refiner/SelectionSetRefiner.php: -------------------------------------------------------------------------------- 1 | selections), 24 | new DuplicateFieldModule($this->selections), 25 | new EmptyFragmentModule($this->selections), 26 | ]; 27 | 28 | foreach ($modules as $module) { 29 | $module->refine(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Value/ListValue.php: -------------------------------------------------------------------------------- 1 | type; 22 | } 23 | 24 | #[\Override] 25 | public function getRawValue(bool $forResolvers = false) : array 26 | { 27 | $return = []; 28 | 29 | foreach ($this->value as $listItem) { 30 | $return[] = $listItem->getRawValue($forResolvers); 31 | } 32 | 33 | return $return; 34 | } 35 | 36 | #[\Override] 37 | public function getIterator() : \ArrayIterator 38 | { 39 | return new \ArrayIterator($this->value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/ComponentVisitor.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface ComponentVisitor extends EntityVisitor 17 | { 18 | /** 19 | * @return T 20 | */ 21 | public function visitField(Field $field) : mixed; 22 | 23 | /** 24 | * @return T 25 | */ 26 | public function visitArgument(Argument $argument) : mixed; 27 | 28 | /** 29 | * @return T 30 | */ 31 | public function visitDirectiveUsage(DirectiveUsage $directiveUsage) : mixed; 32 | 33 | /** 34 | * @return T 35 | */ 36 | public function visitEnumItem(EnumItem $enumItem) : mixed; 37 | } 38 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/IntType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[Description('Int built-in type (32 bit)')] 14 | final class IntType extends ScalarType 15 | { 16 | protected const NAME = 'Int'; 17 | // by specification there is a limit to 32 bits. ExtraTypes package contains a custom BigInt type. 18 | private const INT_LIMIT = 2 ** 31; 19 | 20 | #[\Override] 21 | public function validateAndCoerceInput(mixed $rawValue) : ?int 22 | { 23 | return \is_int($rawValue) && $rawValue >= (- self::INT_LIMIT) && $rawValue <= self::INT_LIMIT 24 | ? $rawValue 25 | : null; 26 | } 27 | 28 | #[\Override] 29 | public function coerceOutput(mixed $rawValue) : int 30 | { 31 | return $rawValue; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | PHP version | Status | 6 | | ------- | ------------------ |-------------|--------| 7 | | > 1.3 | :heavy_check_mark: | 8.1 | Maintained and actively improved with new features. 8 | | 1.3.x | :white_check_mark: | 8.0 | Maintained in bugfix only mode. 9 | | < 1.3 | :x: | 8.0 | Please upgrade to 1.3 or later. 10 | | < 0.25 | :x: | 7.4 | Please upgrade to 1.3 or later. 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Report security bugs by emailing the maintainer at peldax@gmail.com. 15 | 16 | The maintainer will respond to your email as soon as he can, usually within 24 hours. 17 | If the vulnerability is accepted, a bugfix will be released to all actively maintained versions. 18 | 19 | Depending on the severity, GitHub security advisory or `roave/security-advisories` can be used to inform users about the important bugfix release. 20 | -------------------------------------------------------------------------------- /src/Value/InputedValue.php: -------------------------------------------------------------------------------- 1 | metaFields instanceof ResolvableFieldSet) { 18 | $this->metaFields = $this->getMetaFieldDefinition(); 19 | } 20 | 21 | return $this->metaFields; 22 | } 23 | 24 | private function getMetaFieldDefinition() : ResolvableFieldSet 25 | { 26 | return new ResolvableFieldSet([ 27 | new ResolvableField( 28 | '__typename', 29 | Container::String()->notNull(), 30 | function() : string { 31 | return $this->getName(); 32 | }, 33 | ), 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Value/FieldValue.php: -------------------------------------------------------------------------------- 1 | getDirectiveUsages() as $directiveUsage) { 18 | $directive = $directiveUsage->getDirective(); 19 | \assert($directive instanceof FieldDefinitionLocation); 20 | $directive->resolveFieldDefinitionValue($directiveUsage->getArgumentValues(), $this); 21 | } 22 | } 23 | 24 | #[\Override] 25 | public function jsonSerialize() : ResolvedValue 26 | { 27 | return $this->value; 28 | } 29 | 30 | public function getField() : Field 31 | { 32 | return $this->field; 33 | } 34 | 35 | public function getValue() : ResolvedValue 36 | { 37 | return $this->value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Value/Exception/InvalidValue.php: -------------------------------------------------------------------------------- 1 | accept(new PrintNameVisitor()), $this->printValue($rawValue)]); 21 | } 22 | 23 | private function printValue(mixed $rawValue) : string 24 | { 25 | if ($rawValue === null || \is_scalar($rawValue)) { 26 | return \json_encode($rawValue, \JSON_THROW_ON_ERROR); 27 | } 28 | 29 | if (\is_array($rawValue)) { 30 | return 'list'; 31 | } 32 | 33 | if ($rawValue instanceof \stdClass) { 34 | return 'object'; 35 | } 36 | 37 | return $rawValue::class; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Infinityloop.dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Typesystem/Contract/NamedType.php: -------------------------------------------------------------------------------- 1 | $visitor 20 | * @return T 21 | */ 22 | #[\Override] 23 | abstract public function accept(NamedTypeVisitor $visitor) : mixed; 24 | 25 | final public function getName() : string 26 | { 27 | return static::NAME; 28 | } 29 | 30 | final public function notNull() : NotNullType 31 | { 32 | return new NotNullType($this); 33 | } 34 | 35 | final public function notNullList() : NotNullType 36 | { 37 | return new NotNullType(new ListType(new NotNullType($this))); 38 | } 39 | 40 | final public function list() : ListType 41 | { 42 | return new ListType($this); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Typesystem/Location/FieldDefinitionLocation.php: -------------------------------------------------------------------------------- 1 | getCases() as $case) { 25 | $values[] = new EnumItem($case->getBackingValue()); 26 | } 27 | 28 | $ref = new \ReflectionEnum(TypeSystemDirectiveLocation::class); 29 | 30 | foreach ($ref->getCases() as $case) { 31 | $values[] = new EnumItem($case->getBackingValue()); 32 | } 33 | 34 | parent::__construct(new EnumItemSet($values)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Typesystem/Argument/ArgumentSet.php: -------------------------------------------------------------------------------- 1 | defaults; 23 | } 24 | 25 | #[\Override] 26 | public function offsetSet($offset, $value) : void 27 | { 28 | \assert($value instanceof Argument); 29 | 30 | parent::offsetSet($offset, $value); 31 | 32 | $defaultValue = $value->getDefaultValue(); 33 | 34 | if ($defaultValue instanceof ArgumentValue) { 35 | $this->defaults[$value->getName()] = $defaultValue->getValue()->getRawValue(); 36 | } 37 | } 38 | 39 | #[\Override] 40 | protected function getKey(object $object) : string 41 | { 42 | \assert($object instanceof Argument); 43 | 44 | return $object->getName(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Typesystem/EnumItem/EnumItemSet.php: -------------------------------------------------------------------------------- 1 | $data 19 | * @param ?string $enumClass 20 | */ 21 | public function __construct( 22 | array $data = [], 23 | private ?string $enumClass = null, 24 | ) 25 | { 26 | parent::__construct($data); 27 | } 28 | 29 | public function getEnumClass() : ?string 30 | { 31 | return $this->enumClass; 32 | } 33 | 34 | /** 35 | * @return list 36 | */ 37 | public function getArray() : array 38 | { 39 | $return = []; 40 | 41 | foreach ($this as $enumItem) { 42 | $return[] = $enumItem->getName(); 43 | } 44 | 45 | return $return; 46 | } 47 | 48 | #[\Override] 49 | protected function getKey(object $object) : string 50 | { 51 | \assert($object instanceof EnumItem); 52 | 53 | return $object->getName(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Value/NullValue.php: -------------------------------------------------------------------------------- 1 | type; 28 | } 29 | 30 | #[\Override] 31 | public function jsonSerialize() : null 32 | { 33 | return null; 34 | } 35 | 36 | #[\Override] 37 | public function printValue() : string 38 | { 39 | return 'null'; 40 | } 41 | 42 | #[\Override] 43 | public function applyVariables(VariableValueSet $variables) : void 44 | { 45 | // nothing here 46 | } 47 | 48 | #[\Override] 49 | public function resolveRemainingDirectives() : void 50 | { 51 | // nothing here 52 | } 53 | 54 | #[\Override] 55 | public function isSame(Value $compare) : bool 56 | { 57 | return $compare instanceof self; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Normalizer/Visitor/ApplyVariablesVisitor.php: -------------------------------------------------------------------------------- 1 | arguments->applyVariables($this->variables); 25 | $field->directives->applyVariables($this->variables); 26 | $field->children?->applyVariables($this->variables); 27 | 28 | return null; 29 | } 30 | 31 | #[\Override] 32 | public function visitFragmentSpread(FragmentSpread $fragmentSpread) : null 33 | { 34 | $fragmentSpread->children->applyVariables($this->variables); 35 | 36 | return null; 37 | } 38 | 39 | #[\Override] 40 | public function visitInlineFragment(InlineFragment $inlineFragment) : null 41 | { 42 | $inlineFragment->children->applyVariables($this->variables); 43 | 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Typesystem/Utils/TDeprecatable.php: -------------------------------------------------------------------------------- 1 | addDirective( 18 | Container::directiveDeprecated(), 19 | ['reason' => $reason], 20 | ); 21 | 22 | return $this; 23 | } 24 | 25 | public function isDeprecated() : bool 26 | { 27 | foreach ($this->directiveUsages as $directive) { 28 | if ($directive->getDirective() instanceof DeprecatedDirective) { 29 | return true; 30 | } 31 | } 32 | 33 | return false; 34 | } 35 | 36 | public function getDeprecationReason() : ?string 37 | { 38 | foreach ($this->directiveUsages as $directive) { 39 | if ($directive->getDirective() instanceof DeprecatedDirective) { 40 | $value = $directive->getArgumentValues()->offsetGet('reason')->getValue()->getRawValue(); 41 | 42 | return \is_string($value) 43 | ? $value 44 | : null; 45 | } 46 | } 47 | 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Typesystem/UnionType.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 25 | } 26 | 27 | final public function getTypes() : TypeSet 28 | { 29 | return $this->types; 30 | } 31 | 32 | #[\Override] 33 | final public function accept(AbstractTypeVisitor $visitor) : mixed 34 | { 35 | return $visitor->visitUnion($this); 36 | } 37 | 38 | /** 39 | * @param UnionLocation $directive 40 | * @phpcs:ignore 41 | * @param array $arguments 42 | */ 43 | final public function addDirective(UnionLocation $directive, array $arguments = []) : static 44 | { 45 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 46 | 47 | return $this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Value/TypeValue.php: -------------------------------------------------------------------------------- 1 | getDirectiveUsages() as $directiveUsage) { 19 | $directive = $directiveUsage->getDirective(); 20 | \assert($directive instanceof ObjectLocation); 21 | $directive->resolveObject($directiveUsage->getArgumentValues(), $this); 22 | } 23 | } 24 | 25 | #[\Override] 26 | public function getRawValue() : \stdClass 27 | { 28 | return $this->value; 29 | } 30 | 31 | #[\Override] 32 | public function getType() : Type 33 | { 34 | return $this->type; 35 | } 36 | 37 | public function getIntermediateValue() : TypeIntermediateValue 38 | { 39 | return $this->intermediateValue; 40 | } 41 | 42 | #[\Override] 43 | public function jsonSerialize() : \stdClass 44 | { 45 | return $this->value; 46 | } 47 | 48 | public function __get(string $name) : FieldValue 49 | { 50 | return $this->value->{$name}; 51 | } 52 | 53 | public function __isset(string $name) : bool 54 | { 55 | return \property_exists($this->value, $name); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Typesystem/DirectiveUsage/DirectiveUsage.php: -------------------------------------------------------------------------------- 1 | $arguments 22 | */ 23 | public function __construct( 24 | private TypeSystemDirective $directive, 25 | array $arguments, 26 | ) 27 | { 28 | $this->argumentValues = new ArgumentValueSet( 29 | (array) ConvertRawValueVisitor::convertArgumentSet( 30 | $directive->getArguments(), 31 | (object) $arguments, 32 | new Path(), 33 | ), 34 | ); 35 | } 36 | 37 | public function getDirective() : TypeSystemDirective 38 | { 39 | return $this->directive; 40 | } 41 | 42 | public function getArgumentValues() : ArgumentValueSet 43 | { 44 | return $this->argumentValues; 45 | } 46 | 47 | #[\Override] 48 | public function accept(ComponentVisitor $visitor) : mixed 49 | { 50 | return $visitor->visitDirectiveUsage($this); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Typesystem/EnumItem/EnumItem.php: -------------------------------------------------------------------------------- 1 | description = $description; 28 | $this->directiveUsages = new DirectiveUsageSet(); 29 | } 30 | 31 | public function getName() : string 32 | { 33 | return $this->name; 34 | } 35 | 36 | #[\Override] 37 | public function accept(ComponentVisitor $visitor) : mixed 38 | { 39 | return $visitor->visitEnumItem($this); 40 | } 41 | 42 | /** 43 | * @param EnumItemLocation $directive 44 | * @phpcs:ignore 45 | * @param array $arguments 46 | */ 47 | public function addDirective(EnumItemLocation $directive, array $arguments = []) : self 48 | { 49 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 50 | 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/IsInputableVisitor.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final readonly class IsInputableVisitor implements TypeVisitor 21 | { 22 | #[\Override] 23 | public function visitType(Type $type) : false 24 | { 25 | return false; 26 | } 27 | 28 | #[\Override] 29 | public function visitInterface(InterfaceType $interface) : false 30 | { 31 | return false; 32 | } 33 | 34 | #[\Override] 35 | public function visitUnion(UnionType $union) : false 36 | { 37 | return false; 38 | } 39 | 40 | #[\Override] 41 | public function visitInput(InputType $input) : true 42 | { 43 | return true; 44 | } 45 | 46 | #[\Override] 47 | public function visitScalar(ScalarType $scalar) : true 48 | { 49 | return true; 50 | } 51 | 52 | #[\Override] 53 | public function visitEnum(EnumType $enum) : true 54 | { 55 | return true; 56 | } 57 | 58 | #[\Override] 59 | public function visitNotNull(NotNullType $notNull) : bool 60 | { 61 | return $notNull->getInnerType()->accept($this); 62 | } 63 | 64 | #[\Override] 65 | public function visitList(ListType $list) : bool 66 | { 67 | return $list->getInnerType()->accept($this); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/IsOutputableVisitor.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final readonly class IsOutputableVisitor implements TypeVisitor 21 | { 22 | #[\Override] 23 | public function visitType(Type $type) : true 24 | { 25 | return true; 26 | } 27 | 28 | #[\Override] 29 | public function visitInterface(InterfaceType $interface) : true 30 | { 31 | return true; 32 | } 33 | 34 | #[\Override] 35 | public function visitUnion(UnionType $union) : true 36 | { 37 | return true; 38 | } 39 | 40 | #[\Override] 41 | public function visitInput(InputType $input) : false 42 | { 43 | return false; 44 | } 45 | 46 | #[\Override] 47 | public function visitScalar(ScalarType $scalar) : true 48 | { 49 | return true; 50 | } 51 | 52 | #[\Override] 53 | public function visitEnum(EnumType $enum) : true 54 | { 55 | return true; 56 | } 57 | 58 | #[\Override] 59 | public function visitNotNull(NotNullType $notNull) : bool 60 | { 61 | return $notNull->getInnerType()->accept($this); 62 | } 63 | 64 | #[\Override] 65 | public function visitList(ListType $list) : bool 66 | { 67 | return $list->getInnerType()->accept($this); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Normalizer/Refiner/Module/EmptyFragmentModule.php: -------------------------------------------------------------------------------- 1 | selections as $index => $selection) { 27 | $this->index = $index; 28 | $selection->accept($this); 29 | } 30 | } 31 | 32 | #[\Override] 33 | public function visitField(Field $field) : null 34 | { 35 | return null; 36 | } 37 | 38 | #[\Override] 39 | public function visitFragmentSpread(FragmentSpread $fragmentSpread) : null 40 | { 41 | $refiner = new self($fragmentSpread->children); 42 | $refiner->refine(); 43 | 44 | if ($fragmentSpread->children->count() === 0) { 45 | $this->selections->offsetUnset($this->index); 46 | } 47 | 48 | return null; 49 | } 50 | 51 | #[\Override] 52 | public function visitInlineFragment(InlineFragment $inlineFragment) : null 53 | { 54 | $refiner = new self($inlineFragment->children); 55 | $refiner->refine(); 56 | 57 | if ($inlineFragment->children->count() === 0) { 58 | $this->selections->offsetUnset($this->index); 59 | } 60 | 61 | return null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Value/ListInputedValue.php: -------------------------------------------------------------------------------- 1 | value as $value) { 17 | \assert($value instanceof InputedValue); 18 | 19 | $component[] = $value->printValue(); 20 | } 21 | 22 | return '[' . \implode(',', $component) . ']'; 23 | } 24 | 25 | #[\Override] 26 | public function applyVariables(VariableValueSet $variables) : void 27 | { 28 | foreach ($this->value as $value) { 29 | \assert($value instanceof InputedValue); 30 | 31 | $value->applyVariables($variables); 32 | } 33 | } 34 | 35 | #[\Override] 36 | public function resolveRemainingDirectives() : void 37 | { 38 | foreach ($this->value as $value) { 39 | \assert($value instanceof InputedValue); 40 | 41 | $value->resolveRemainingDirectives(); 42 | } 43 | } 44 | 45 | #[\Override] 46 | public function isSame(Value $compare) : bool 47 | { 48 | if (!$compare instanceof self) { 49 | return false; 50 | } 51 | 52 | $secondArray = $compare->value; 53 | 54 | if (\count($secondArray) !== \count($this->value)) { 55 | return false; 56 | } 57 | 58 | foreach ($this->value as $key => $value) { 59 | \assert($value instanceof InputedValue); 60 | 61 | if (!\array_key_exists($key, $secondArray) || !$value->isSame($secondArray[$key])) { 62 | return false; 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/TypeKindVisitor.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final class TypeKindVisitor implements TypeVisitor 22 | { 23 | #[\Override] 24 | public function visitType(Type $type) : string 25 | { 26 | return TypeKind::OBJECT; 27 | } 28 | 29 | #[\Override] 30 | public function visitInterface(InterfaceType $interface) : string 31 | { 32 | return TypeKind::INTERFACE; 33 | } 34 | 35 | #[\Override] 36 | public function visitUnion(UnionType $union) : string 37 | { 38 | return TypeKind::UNION; 39 | } 40 | 41 | #[\Override] 42 | public function visitInput(InputType $input) : string 43 | { 44 | return TypeKind::INPUT_OBJECT; 45 | } 46 | 47 | #[\Override] 48 | public function visitScalar(ScalarType $scalar) : string 49 | { 50 | return TypeKind::SCALAR; 51 | } 52 | 53 | #[\Override] 54 | public function visitEnum(EnumType $enum) : string 55 | { 56 | return TypeKind::ENUM; 57 | } 58 | 59 | #[\Override] 60 | public function visitNotNull(NotNullType $notNull) : string 61 | { 62 | return TypeKind::NON_NULL; 63 | } 64 | 65 | #[\Override] 66 | public function visitList(ListType $list) : string 67 | { 68 | return TypeKind::LIST; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/GetShapingTypeVisitor.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final readonly class GetShapingTypeVisitor implements TypeVisitor 22 | { 23 | #[\Override] 24 | public function visitType(Type $type) : Type 25 | { 26 | return $type; 27 | } 28 | 29 | #[\Override] 30 | public function visitInterface(InterfaceType $interface) : InterfaceType 31 | { 32 | return $interface; 33 | } 34 | 35 | #[\Override] 36 | public function visitUnion(UnionType $union) : UnionType 37 | { 38 | return $union; 39 | } 40 | 41 | #[\Override] 42 | public function visitInput(InputType $input) : InputType 43 | { 44 | return $input; 45 | } 46 | 47 | #[\Override] 48 | public function visitScalar(ScalarType $scalar) : ScalarType 49 | { 50 | return $scalar; 51 | } 52 | 53 | #[\Override] 54 | public function visitEnum(EnumType $enum) : EnumType 55 | { 56 | return $enum; 57 | } 58 | 59 | #[\Override] 60 | public function visitNotNull(NotNullType $notNull) : TypeContract 61 | { 62 | return $notNull->getInnerType()->accept($this); 63 | } 64 | 65 | #[\Override] 66 | public function visitList(ListType $list) : ListType 67 | { 68 | return $list; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/GetNamedTypeVisitor.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final readonly class GetNamedTypeVisitor implements TypeVisitor 22 | { 23 | #[\Override] 24 | public function visitType(Type $type) : Type 25 | { 26 | return $type; 27 | } 28 | 29 | #[\Override] 30 | public function visitInterface(InterfaceType $interface) : InterfaceType 31 | { 32 | return $interface; 33 | } 34 | 35 | #[\Override] 36 | public function visitUnion(UnionType $union) : UnionType 37 | { 38 | return $union; 39 | } 40 | 41 | #[\Override] 42 | public function visitInput(InputType $input) : InputType 43 | { 44 | return $input; 45 | } 46 | 47 | #[\Override] 48 | public function visitScalar(ScalarType $scalar) : ScalarType 49 | { 50 | return $scalar; 51 | } 52 | 53 | #[\Override] 54 | public function visitEnum(EnumType $enum) : EnumType 55 | { 56 | return $enum; 57 | } 58 | 59 | #[\Override] 60 | public function visitNotNull(NotNullType $notNull) : NamedType 61 | { 62 | return $notNull->getInnerType()->accept($this); 63 | } 64 | 65 | #[\Override] 66 | public function visitList(ListType $list) : NamedType 67 | { 68 | return $list->getInnerType()->accept($this); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/PrintNameVisitor.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final readonly class PrintNameVisitor implements TypeVisitor 21 | { 22 | #[\Override] 23 | public function visitType(Type $type) : string 24 | { 25 | return $type->getName(); 26 | } 27 | 28 | #[\Override] 29 | public function visitInterface(InterfaceType $interface) : string 30 | { 31 | return $interface->getName(); 32 | } 33 | 34 | #[\Override] 35 | public function visitUnion(UnionType $union) : string 36 | { 37 | return $union->getName(); 38 | } 39 | 40 | #[\Override] 41 | public function visitInput(InputType $input) : string 42 | { 43 | return $input->getName(); 44 | } 45 | 46 | #[\Override] 47 | public function visitScalar(ScalarType $scalar) : string 48 | { 49 | return $scalar->getName(); 50 | } 51 | 52 | #[\Override] 53 | public function visitEnum(EnumType $enum) : string 54 | { 55 | return $enum->getName(); 56 | } 57 | 58 | #[\Override] 59 | public function visitNotNull(NotNullType $notNull) : string 60 | { 61 | return $notNull->getInnerType()->accept($this) . '!'; 62 | } 63 | 64 | #[\Override] 65 | public function visitList(ListType $list) : string 66 | { 67 | return '[' . $list->getInnerType()->accept($this) . ']'; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/IsImplementedByVisitor.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class IsImplementedByVisitor implements AbstractTypeVisitor 17 | { 18 | private TypeContract $typeToCompare; 19 | 20 | public function __construct( 21 | TypeContract $typeToCompare, 22 | ) 23 | { 24 | $this->typeToCompare = $typeToCompare->accept(new GetShapingTypeVisitor()); 25 | } 26 | 27 | #[\Override] 28 | public function visitInterface(InterfaceType $interface) : bool 29 | { 30 | return $this->typeToCompare instanceof InterfaceImplementor 31 | && self::implements($this->typeToCompare, $interface); 32 | } 33 | 34 | #[\Override] 35 | public function visitUnion(UnionType $union) : bool 36 | { 37 | foreach ($union->getTypes() as $unionItem) { 38 | if ($unionItem::class === $this->typeToCompare::class) { 39 | return true; 40 | } 41 | } 42 | 43 | return false; 44 | } 45 | 46 | /** 47 | * Checks whether given type implements given interface. 48 | * @param InterfaceImplementor $implementor 49 | * @param InterfaceType $interface 50 | */ 51 | public static function implements(InterfaceImplementor $implementor, InterfaceType $interface) : bool 52 | { 53 | foreach ($implementor->getInterfaces() as $temp) { 54 | if ($temp::class === $interface::class || self::implements($temp, $interface)) { 55 | return true; 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Value/ArgumentValue.php: -------------------------------------------------------------------------------- 1 | hasVariables) { 20 | $this->resolvePureDirectives(); 21 | } 22 | } 23 | 24 | public function getValue() : InputedValue 25 | { 26 | return $this->value; 27 | } 28 | 29 | public function getArgument() : Argument 30 | { 31 | return $this->argument; 32 | } 33 | 34 | public function applyVariables(VariableValueSet $variables) : void 35 | { 36 | if ($this->hasVariables) { 37 | $this->value->applyVariables($variables); 38 | $this->resolvePureDirectives(); 39 | } 40 | } 41 | 42 | public function resolvePureDirectives() : void 43 | { 44 | foreach ($this->argument->getDirectiveUsages() as $directiveUsage) { 45 | $directive = $directiveUsage->getDirective(); 46 | \assert($directive instanceof ArgumentDefinitionLocation); 47 | 48 | if ($directive::isPure()) { 49 | $directive->resolveArgumentDefinition($directiveUsage->getArgumentValues(), $this); 50 | } 51 | } 52 | } 53 | 54 | public function resolveNonPureDirectives() : void 55 | { 56 | $this->value->resolveRemainingDirectives(); 57 | 58 | foreach ($this->argument->getDirectiveUsages() as $directiveUsage) { 59 | $directive = $directiveUsage->getDirective(); 60 | \assert($directive instanceof ArgumentDefinitionLocation); 61 | 62 | if (!$directive::isPure()) { 63 | $directive->resolveArgumentDefinition($directiveUsage->getArgumentValues(), $this); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Typesystem/Introspection/EnumValue.php: -------------------------------------------------------------------------------- 1 | notNull(), 37 | static function (EnumItem $item) : string { 38 | return $item->getName(); 39 | }, 40 | ), 41 | ResolvableField::create( 42 | 'description', 43 | Container::String(), 44 | static function (EnumItem $item) : ?string { 45 | return $item->getDescription(); 46 | }, 47 | ), 48 | ResolvableField::create( 49 | 'isDeprecated', 50 | Container::Boolean()->notNull(), 51 | static function (EnumItem $item) : bool { 52 | return $item->isDeprecated(); 53 | }, 54 | ), 55 | ResolvableField::create( 56 | 'deprecationReason', 57 | Container::String(), 58 | static function (EnumItem $item) : ?string { 59 | return $item->getDeprecationReason(); 60 | }, 61 | ), 62 | ]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Value/ArgumentValueSet.php: -------------------------------------------------------------------------------- 1 | $argumentValue) { 23 | $return[$name] = $argumentValue->getValue()->getRawValue(true); 24 | } 25 | 26 | return $return; 27 | } 28 | 29 | public function applyVariables(VariableValueSet $variables) : void 30 | { 31 | foreach ($this as $value) { 32 | $value->applyVariables($variables); 33 | } 34 | } 35 | 36 | public function isSame(self $compare) : bool 37 | { 38 | foreach ($compare as $lhs) { 39 | if ($this->offsetExists($lhs->getArgument()->getName())) { 40 | if ($lhs->getValue()->isSame($this->offsetGet($lhs->getArgument()->getName())->getValue())) { 41 | continue; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | if ($lhs->getValue()->isSame($lhs->getArgument()->getDefaultValue()?->getValue())) { 48 | continue; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | foreach ($this as $lhs) { 55 | if ($compare->offsetExists($lhs->getArgument()->getName()) || 56 | $lhs->getValue()->isSame($lhs->getArgument()->getDefaultValue()?->getValue())) { 57 | continue; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | #[\Override] 67 | protected function getKey(object $object) : string 68 | { 69 | \assert($object instanceof ArgumentValue); 70 | 71 | return $object->getArgument()->getName(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/OneOfDirective.php: -------------------------------------------------------------------------------- 1 | getArguments() as $argument) { 29 | if ($argument->getType() instanceof NotNullType || 30 | $argument->getDefaultValue() instanceof ArgumentValue) { 31 | throw new OneOfInputInvalidFields(); 32 | } 33 | } 34 | 35 | return true; 36 | } 37 | 38 | #[\Override] 39 | public function resolveInputObject(ArgumentValueSet $arguments, InputValue $inputValue) : void 40 | { 41 | $currentCount = 0; 42 | 43 | foreach ($inputValue as $innerValue) { 44 | \assert($innerValue instanceof ArgumentValue); 45 | 46 | if ($currentCount >= 1 || $innerValue->getValue() instanceof NullValue) { 47 | throw new OneOfDirectiveNotSatisfied(); 48 | } 49 | 50 | ++$currentCount; 51 | } 52 | 53 | if ($currentCount !== 1) { 54 | throw new OneOfDirectiveNotSatisfied(); 55 | } 56 | } 57 | 58 | #[\Override] 59 | protected function getFieldDefinition() : ArgumentSet 60 | { 61 | return new ArgumentSet([]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Normalizer/Validator/FragmentCycleValidator.php: -------------------------------------------------------------------------------- 1 | fragmentSet as $fragment) { 28 | $this->validateFragment($fragment); 29 | } 30 | } 31 | 32 | private function validateFragment(Fragment $fragment) : void 33 | { 34 | if (\array_key_exists($fragment->name, $this->validated)) { 35 | return; 36 | } 37 | 38 | if (\array_key_exists($fragment->name, $this->stack)) { 39 | throw new FragmentCycle(); 40 | } 41 | 42 | $this->stack[$fragment->name] = true; 43 | $this->validateFieldSet($fragment->fields); 44 | unset($this->stack[$fragment->name]); 45 | $this->validated[$fragment->name] = true; 46 | } 47 | 48 | private function validateFieldSet(FieldSet $fieldSet) : void 49 | { 50 | foreach ($fieldSet as $field) { 51 | if ($field->children instanceof FieldSet) { 52 | $this->validateFieldSet($field->children); 53 | } 54 | } 55 | 56 | foreach ($fieldSet->getFragmentSpreads() as $spread) { 57 | if (!$spread instanceof NamedFragmentSpread) { 58 | continue; 59 | } 60 | 61 | if (!$this->fragmentSet->offsetExists($spread->name)) { 62 | throw new UnknownFragment($spread->name); 63 | } 64 | 65 | $this->validateFragment($this->fragmentSet->offsetGet($spread->name)); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Normalizer/Visitor/GetFieldVisitor.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | final readonly class GetFieldVisitor implements NamedTypeVisitor 23 | { 24 | public function __construct( 25 | private string $name, 26 | ) 27 | { 28 | } 29 | 30 | #[\Override] 31 | public function visitType(Type $type) : Field 32 | { 33 | return $type->getMetaFields()[$this->name] 34 | ?? $type->getFields()[$this->name] 35 | ?? throw new UnknownField($this->name, $type->getName()); 36 | } 37 | 38 | #[\Override] 39 | public function visitInterface(InterfaceType $interface) : Field 40 | { 41 | return $interface->getMetaFields()[$this->name] 42 | ?? $interface->getFields()[$this->name] 43 | ?? throw new UnknownField($this->name, $interface->getName()); 44 | } 45 | 46 | #[\Override] 47 | public function visitUnion(UnionType $union) : Field 48 | { 49 | return $union->getMetaFields()[$this->name] 50 | ?? throw new SelectionOnUnion(); 51 | } 52 | 53 | #[\Override] 54 | public function visitInput(InputType $input) : never 55 | { 56 | throw new \LogicException(); // @codeCoverageIgnore 57 | } 58 | 59 | #[\Override] 60 | public function visitScalar(ScalarType $scalar) : Field 61 | { 62 | throw new SelectionOnLeaf(); 63 | } 64 | 65 | #[\Override] 66 | public function visitEnum(EnumType $enum) : Field 67 | { 68 | throw new SelectionOnLeaf(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Value/VariableValue.php: -------------------------------------------------------------------------------- 1 | type->accept(new IsInputableVisitor()); 24 | $isCompatible = $variable->type->accept(new IsInstanceOfVisitor($type)); 25 | 26 | if (!$isInputable || !$isCompatible) { 27 | throw new VariableTypeMismatch(); 28 | } 29 | } 30 | 31 | #[\Override] 32 | public function getRawValue(bool $forResolvers = false) : mixed 33 | { 34 | return $this->value->getRawValue($forResolvers); 35 | } 36 | 37 | public function getConcreteValue() : InputedValue 38 | { 39 | return $this->value; 40 | } 41 | 42 | public function getVariable() : Variable 43 | { 44 | return $this->variable; 45 | } 46 | 47 | #[\Override] 48 | public function getType() : Type 49 | { 50 | return $this->type; 51 | } 52 | 53 | #[\Override] 54 | public function printValue() : string 55 | { 56 | throw new \RuntimeException('Not implemented'); 57 | } 58 | 59 | #[\Override] 60 | public function applyVariables(VariableValueSet $variables) : void 61 | { 62 | $this->value = $variables->get($this->variable->name); 63 | } 64 | 65 | #[\Override] 66 | public function resolveRemainingDirectives() : void 67 | { 68 | $this->value->resolveRemainingDirectives(); 69 | } 70 | 71 | #[\Override] 72 | public function isSame(Value $compare) : bool 73 | { 74 | return $compare instanceof self 75 | && $compare->variable->name === $this->variable->name; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Normalizer/Refiner/Module/DuplicateFragmentSpreadModule.php: -------------------------------------------------------------------------------- 1 | visitedFragments = []; 28 | 29 | foreach ($this->selections as $index => $selection) { 30 | $this->index = $index; 31 | $selection->accept($this); 32 | } 33 | } 34 | 35 | #[\Override] 36 | public function visitField(Field $field) : null 37 | { 38 | return null; 39 | } 40 | 41 | #[\Override] 42 | public function visitFragmentSpread(FragmentSpread $fragmentSpread) : null 43 | { 44 | if (!\array_key_exists($fragmentSpread->name, $this->visitedFragments)) { 45 | $this->visitedFragments[$fragmentSpread->name] = true; 46 | 47 | return null; 48 | } 49 | 50 | /** Found identical fragment spread, we can safely exclude it */ 51 | $this->selections->offsetUnset($this->index); 52 | 53 | return null; 54 | } 55 | 56 | #[\Override] 57 | public function visitInlineFragment(InlineFragment $inlineFragment) : null 58 | { 59 | $oldSelections = $this->selections; 60 | $oldIndex = $this->index; 61 | 62 | $this->selections = $inlineFragment->children; 63 | 64 | foreach ($inlineFragment->children as $index => $selection) { 65 | $this->index = $index; 66 | $selection->accept($this); 67 | } 68 | 69 | $this->selections = $oldSelections; 70 | $this->index = $oldIndex; 71 | 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Request/PsrRequestFactory.php: -------------------------------------------------------------------------------- 1 | request->getMethod(); 25 | 26 | if (!\in_array($method, ['GET', 'POST'], true)) { 27 | throw new InvalidMethod(); 28 | } 29 | 30 | $contentTypes = $this->request->getHeader('Content-Type'); 31 | $contentType = \array_pop($contentTypes); 32 | 33 | if (\is_string($contentType) && \str_starts_with($contentType, 'multipart/form-data')) { 34 | if ($method === 'POST' && \array_key_exists('operations', $this->request->getParsedBody())) { 35 | return $this->applyJsonFactory(Json::fromString($this->request->getParsedBody()['operations'])); 36 | } 37 | 38 | throw new InvalidMultipartRequest(); 39 | } 40 | 41 | switch ($contentType) { 42 | case 'application/graphql': 43 | return new Request($this->request->getBody()->getContents()); 44 | case 'application/json': 45 | return $this->applyJsonFactory(Json::fromString($this->request->getBody()->getContents())); 46 | default: 47 | $params = $this->request->getQueryParams(); 48 | 49 | if (\array_key_exists('variables', $params)) { 50 | $params['variables'] = Json::fromString($params['variables'])->toNative(); 51 | } 52 | 53 | return $this->applyJsonFactory(Json::fromNative((object) $params)); 54 | } 55 | } 56 | 57 | private function applyJsonFactory(Json $json) : Request 58 | { 59 | return (new JsonRequestFactory($json, $this->strict))->create(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Typesystem/InterfaceType.php: -------------------------------------------------------------------------------- 1 | implements = $implements; 29 | $this->directiveUsages = new DirectiveUsageSet(); 30 | } 31 | 32 | #[\Override] 33 | final public function getFields() : FieldSet 34 | { 35 | if (!$this->fields instanceof FieldSet) { 36 | $this->fields = new FieldSet([]); 37 | 38 | foreach ($this->implements as $interfaceType) { 39 | $this->fields->merge($interfaceType->getFields(), true); 40 | } 41 | 42 | $this->fields->merge($this->getFieldDefinition(), true); 43 | } 44 | 45 | return $this->fields; 46 | } 47 | 48 | #[\Override] 49 | final public function accept(AbstractTypeVisitor $visitor) : mixed 50 | { 51 | return $visitor->visitInterface($this); 52 | } 53 | 54 | /** 55 | * @param ObjectLocation $directive 56 | * @phpcs:ignore 57 | * @param array $arguments 58 | */ 59 | final public function addDirective(ObjectLocation $directive, array $arguments = []) : static 60 | { 61 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 62 | 63 | return $this; 64 | } 65 | 66 | abstract protected function getFieldDefinition() : FieldSet; 67 | } 68 | -------------------------------------------------------------------------------- /src/Normalizer/Refiner/Module/DuplicateFieldModule.php: -------------------------------------------------------------------------------- 1 | fieldForName = []; 29 | 30 | foreach ($this->selections as $index => $selection) { 31 | $this->index = $index; 32 | $selection->accept($this); 33 | } 34 | } 35 | 36 | #[\Override] 37 | public function visitField(Field $field) : null 38 | { 39 | if (!\array_key_exists($field->outputName, $this->fieldForName)) { 40 | $this->fieldForName[$field->outputName] = $field; 41 | 42 | return null; 43 | } 44 | 45 | /** Merge duplicate field together */ 46 | if ($field->children instanceof SelectionSet) { 47 | $conflict = $this->fieldForName[$field->outputName]; 48 | \assert($conflict instanceof Field); 49 | \assert($conflict->children instanceof SelectionSet); 50 | $mergedSelectionSet = $conflict->children->merge($field->children); 51 | $refiner = new SelectionSetRefiner($mergedSelectionSet); 52 | $refiner->refine(); 53 | } 54 | 55 | /** Exclude duplicate field */ 56 | $this->selections->offsetUnset($this->index); 57 | 58 | return null; 59 | } 60 | 61 | #[\Override] 62 | public function visitFragmentSpread(FragmentSpread $fragmentSpread) : null 63 | { 64 | return null; 65 | } 66 | 67 | #[\Override] 68 | public function visitInlineFragment(InlineFragment $inlineFragment) : null 69 | { 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Typesystem/Field/Field.php: -------------------------------------------------------------------------------- 1 | arguments = new ArgumentSet([]); 32 | $this->directiveUsages = new DirectiveUsageSet(); 33 | } 34 | 35 | public static function create(string $name, Type $type) : self 36 | { 37 | return new self($name, $type); 38 | } 39 | 40 | final public function getName() : string 41 | { 42 | return $this->name; 43 | } 44 | 45 | final public function getType() : Type 46 | { 47 | return $this->type; 48 | } 49 | 50 | final public function getArguments() : ArgumentSet 51 | { 52 | return $this->arguments; 53 | } 54 | 55 | final public function setArguments(ArgumentSet $arguments) : static 56 | { 57 | $this->arguments = $arguments; 58 | 59 | return $this; 60 | } 61 | 62 | #[\Override] 63 | final public function accept(ComponentVisitor $visitor) : mixed 64 | { 65 | return $visitor->visitField($this); 66 | } 67 | 68 | /** 69 | * @param FieldDefinitionLocation $directive 70 | * @phpcs:ignore 71 | * @param array $arguments 72 | */ 73 | final public function addDirective(FieldDefinitionLocation $directive, array $arguments = []) : self 74 | { 75 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 76 | 77 | return $this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Request/JsonRequestFactory.php: -------------------------------------------------------------------------------- 1 | json[self::QUERY])) { 36 | throw new QueryMissing(); 37 | } 38 | 39 | if (!\is_string($this->json[self::QUERY])) { 40 | throw new QueryNotString(); 41 | } 42 | 43 | if (isset($this->json[self::VARIABLES]) && !$this->json[self::VARIABLES] instanceof \stdClass) { 44 | throw new VariablesNotObject(); 45 | } 46 | 47 | if (isset($this->json[self::OPERATION_NAME]) && !\is_string($this->json[self::OPERATION_NAME])) { 48 | throw new OperationNameNotString(); 49 | } 50 | 51 | if ($this->strict) { 52 | foreach ($this->json as $key => $value) { 53 | if (!\in_array($key, [self::QUERY, self::VARIABLES, self::OPERATION_NAME], true)) { 54 | throw new UnknownKey(); 55 | } 56 | } 57 | } 58 | 59 | $query = $this->json[self::QUERY]; 60 | $variables = $this->json[self::VARIABLES] 61 | ?? new \stdClass(); 62 | $operationName = $this->json[self::OPERATION_NAME] 63 | ?? null; 64 | 65 | return new Request($query, $variables, $operationName); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Typesystem/ScalarType.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 27 | } 28 | 29 | #[\Override] 30 | final public function accept(NamedTypeVisitor $visitor) : mixed 31 | { 32 | return $visitor->visitScalar($this); 33 | } 34 | 35 | /** 36 | * @phpcs:ignore 37 | * @param mixed $rawValue 38 | * @return ?T 39 | */ 40 | abstract public function validateAndCoerceInput(mixed $rawValue) : mixed; 41 | 42 | /** 43 | * @param T $rawValue 44 | */ 45 | abstract public function coerceOutput(mixed $rawValue) : string|int|float|bool; 46 | 47 | /** 48 | * @param ScalarLocation $directive 49 | * @phpcs:ignore 50 | * @param array $arguments 51 | */ 52 | final public function addDirective(ScalarLocation $directive, array $arguments = []) : static 53 | { 54 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 55 | 56 | return $this; 57 | } 58 | 59 | public function setSpecifiedBy(string $url) : self 60 | { 61 | $this->addDirective( 62 | Container::directiveSpecifiedBy(), 63 | ['url' => $url], 64 | ); 65 | 66 | return $this; 67 | } 68 | 69 | public function getSpecifiedByUrl() : ?string 70 | { 71 | foreach ($this->getDirectiveUsages() as $directive) { 72 | if ($directive->getDirective() instanceof SpecifiedByDirective) { 73 | return $directive->getArgumentValues()->offsetGet('url')->getValue()->getRawValue(); 74 | } 75 | } 76 | 77 | return null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Value/ScalarValue.php: -------------------------------------------------------------------------------- 1 | rawValue = $type->validateAndCoerceInput($rawValue) 24 | ?? throw new InvalidValue($type, $rawValue, $inputed); 25 | } 26 | 27 | #[\Override] 28 | public function getRawValue(bool $forResolvers = false) : mixed 29 | { 30 | return ($forResolvers && $this->hasResolverValue) 31 | ? $this->resolverValue 32 | : $this->rawValue; 33 | } 34 | 35 | #[\Override] 36 | public function getType() : ScalarType 37 | { 38 | return $this->type; 39 | } 40 | 41 | #[\Override] 42 | public function jsonSerialize() : string|int|float|bool 43 | { 44 | return $this->type->coerceOutput($this->rawValue); 45 | } 46 | 47 | #[\Override] 48 | public function printValue() : string 49 | { 50 | return \json_encode($this->jsonSerialize(), \JSON_THROW_ON_ERROR | 51 | \JSON_UNESCAPED_UNICODE | 52 | \JSON_UNESCAPED_SLASHES | 53 | \JSON_PRESERVE_ZERO_FRACTION); 54 | } 55 | 56 | #[\Override] 57 | public function applyVariables(VariableValueSet $variables) : void 58 | { 59 | // nothing here 60 | } 61 | 62 | #[\Override] 63 | public function resolveRemainingDirectives() : void 64 | { 65 | // nothing here 66 | } 67 | 68 | #[\Override] 69 | public function isSame(Value $compare) : bool 70 | { 71 | return $compare instanceof self 72 | && $this->rawValue === $compare->getRawValue(); 73 | } 74 | 75 | public function setResolverValue(mixed $value) : void 76 | { 77 | $this->hasResolverValue = true; 78 | $this->resolverValue = $value; 79 | } 80 | 81 | public function hasResolverValue() : bool 82 | { 83 | return $this->hasResolverValue; 84 | } 85 | 86 | public function getResolverValue() : mixed 87 | { 88 | return $this->resolverValue; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Typesystem/Argument/Argument.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 34 | } 35 | 36 | public static function create(string $name, Type $type) : self 37 | { 38 | return new self($name, $type); 39 | } 40 | 41 | public function getName() : string 42 | { 43 | return $this->name; 44 | } 45 | 46 | public function getType() : Type 47 | { 48 | return $this->type; 49 | } 50 | 51 | public function getDefaultValue() : ?ArgumentValue 52 | { 53 | return $this->defaultValue; 54 | } 55 | 56 | public function setDefaultValue(mixed $defaultValue) : self 57 | { 58 | $this->defaultValue = new ArgumentValue( 59 | $this, 60 | $this->getType()->accept(new ConvertRawValueVisitor($defaultValue, new Path())), 61 | false, 62 | ); 63 | 64 | return $this; 65 | } 66 | 67 | #[\Override] 68 | public function accept(ComponentVisitor $visitor) : mixed 69 | { 70 | return $visitor->visitArgument($this); 71 | } 72 | 73 | /** 74 | * @param ArgumentDefinitionLocation $directive 75 | * @phpcs:ignore 76 | * @param array $arguments 77 | */ 78 | public function addDirective(ArgumentDefinitionLocation $directive, array $arguments = []) : self 79 | { 80 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 81 | 82 | return $this; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Typesystem/InputType.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 27 | } 28 | 29 | final public function getArguments() : ArgumentSet 30 | { 31 | if (!$this->arguments instanceof ArgumentSet) { 32 | $this->arguments = $this->getFieldDefinition(); 33 | $this->afterGetFieldDefinition(); 34 | } 35 | 36 | return $this->arguments; 37 | } 38 | 39 | #[\Override] 40 | final public function accept(NamedTypeVisitor $visitor) : mixed 41 | { 42 | return $visitor->visitInput($this); 43 | } 44 | 45 | final public function getDataClass() : string 46 | { 47 | return static::DATA_CLASS; 48 | } 49 | 50 | /** 51 | * @param InputObjectLocation $directive 52 | * @phpcs:ignore 53 | * @param array $arguments 54 | */ 55 | final public function addDirective(InputObjectLocation $directive, array $arguments = []) : static 56 | { 57 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 58 | 59 | return $this; 60 | } 61 | 62 | public function isOneOf() : bool 63 | { 64 | foreach ($this->getDirectiveUsages() as $directive) { 65 | if ($directive->getDirective() instanceof OneOfDirective) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | abstract protected function getFieldDefinition() : ArgumentSet; 74 | 75 | /** 76 | * This function serves to prevent infinite cycles. 77 | * 78 | * It doesn't have to be used at all, unless input have arguments self referencing fields and wish to put default value for them. 79 | */ 80 | protected function afterGetFieldDefinition() : void 81 | { 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/SkipDirective.php: -------------------------------------------------------------------------------- 1 | offsetGet('if')->getValue()->getRawValue() === true 38 | ? SelectionDirectiveResult::SKIP 39 | : SelectionDirectiveResult::NONE; 40 | } 41 | 42 | #[\Override] 43 | public function resolveFieldAfter(ArgumentValueSet $arguments, FieldValue $fieldValue) : SelectionDirectiveResult 44 | { 45 | return SelectionDirectiveResult::NONE; 46 | } 47 | 48 | #[\Override] 49 | public function resolveFragmentSpreadBefore(ArgumentValueSet $arguments) : SelectionDirectiveResult 50 | { 51 | return $this->resolveFieldBefore($arguments); 52 | } 53 | 54 | #[\Override] 55 | public function resolveFragmentSpreadAfter(ArgumentValueSet $arguments) : void 56 | { 57 | // nothing here 58 | } 59 | 60 | #[\Override] 61 | public function resolveInlineFragmentBefore(ArgumentValueSet $arguments) : SelectionDirectiveResult 62 | { 63 | return $this->resolveFieldBefore($arguments); 64 | } 65 | 66 | #[\Override] 67 | public function resolveInlineFragmentAfter(ArgumentValueSet $arguments) : void 68 | { 69 | // nothing here 70 | } 71 | 72 | #[\Override] 73 | protected function getFieldDefinition() : ArgumentSet 74 | { 75 | return new ArgumentSet([ 76 | new Argument('if', Container::Boolean()->notNull()), 77 | ]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Typesystem/Introspection/Directive.php: -------------------------------------------------------------------------------- 1 | notNull(), 40 | static function (DirectiveDef $directive) : string { 41 | return $directive->getName(); 42 | }, 43 | ), 44 | ResolvableField::create( 45 | 'description', 46 | Container::String(), 47 | static function (DirectiveDef $directive) : ?string { 48 | return $directive->getDescription(); 49 | }, 50 | ), 51 | ResolvableField::create( 52 | 'locations', 53 | $this->container->getType('__DirectiveLocation')->notNullList(), 54 | static function (DirectiveDef $directive) : array { 55 | return $directive->getLocations(); 56 | }, 57 | ), 58 | ResolvableField::create( 59 | 'args', 60 | $this->container->getType('__InputValue')->notNullList(), 61 | static function (DirectiveDef $directive) : ArgumentSet { 62 | return $directive->getArguments(); 63 | }, 64 | ), 65 | ResolvableField::create( 66 | 'isRepeatable', 67 | Container::Boolean()->notNull(), 68 | static function (DirectiveDef $directive) : bool { 69 | return $directive->isRepeatable(); 70 | }, 71 | ), 72 | ]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/IncludeDirective.php: -------------------------------------------------------------------------------- 1 | offsetGet('if')->getValue()->getRawValue() === true 38 | ? SelectionDirectiveResult::NONE 39 | : SelectionDirectiveResult::SKIP; 40 | } 41 | 42 | #[\Override] 43 | public function resolveFieldAfter(ArgumentValueSet $arguments, FieldValue $fieldValue) : SelectionDirectiveResult 44 | { 45 | return SelectionDirectiveResult::NONE; 46 | } 47 | 48 | #[\Override] 49 | public function resolveFragmentSpreadBefore(ArgumentValueSet $arguments) : SelectionDirectiveResult 50 | { 51 | return $this->resolveFieldBefore($arguments); 52 | } 53 | 54 | #[\Override] 55 | public function resolveFragmentSpreadAfter(ArgumentValueSet $arguments) : void 56 | { 57 | // nothing here 58 | } 59 | 60 | #[\Override] 61 | public function resolveInlineFragmentBefore(ArgumentValueSet $arguments) : SelectionDirectiveResult 62 | { 63 | return $this->resolveFieldBefore($arguments); 64 | } 65 | 66 | #[\Override] 67 | public function resolveInlineFragmentAfter(ArgumentValueSet $arguments) : void 68 | { 69 | // nothing here 70 | } 71 | 72 | #[\Override] 73 | protected function getFieldDefinition() : ArgumentSet 74 | { 75 | return new ArgumentSet([ 76 | new Argument('if', Container::Boolean()->notNull()), 77 | ]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Typesystem/Schema.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 32 | $query->addMetaField(ResolvableField::create( 33 | '__schema', 34 | $container->getType('__Schema')->notNull(), 35 | function() : Schema { 36 | return $this; 37 | }, 38 | )); 39 | $query->addMetaField(ResolvableField::create( 40 | '__type', 41 | $container->getType('__Type'), 42 | function($parent, string $name) : ?TypeContract { 43 | return $this->container->getType($name); 44 | }, 45 | )->setArguments(new ArgumentSet([ 46 | Argument::create('name', Container::String()->notNull()), 47 | ]))); 48 | } 49 | 50 | final public function getContainer() : Container 51 | { 52 | return $this->container; 53 | } 54 | 55 | final public function getQuery() : Type 56 | { 57 | return $this->query; 58 | } 59 | 60 | final public function getMutation() : ?Type 61 | { 62 | return $this->mutation; 63 | } 64 | 65 | final public function getSubscription() : ?Type 66 | { 67 | return $this->subscription; 68 | } 69 | 70 | #[\Override] 71 | final public function accept(EntityVisitor $visitor) : mixed 72 | { 73 | return $visitor->visitSchema($this); 74 | } 75 | 76 | /** 77 | * @param SchemaLocation $directive 78 | * @phpcs:ignore 79 | * @param array $arguments 80 | */ 81 | final public function addDirective(SchemaLocation $directive, array $arguments = []) : self 82 | { 83 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 84 | 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Normalizer/Finalizer.php: -------------------------------------------------------------------------------- 1 | path = new Path(); 22 | 23 | try { 24 | $operation = $this->selectOperation($normalizedRequest, $operationName); 25 | $this->path->add($operation->name . ' '); 26 | $this->applyVariables($operation, $variables); 27 | } catch (GraphpinatorBase $e) { 28 | throw $e->setPath($this->path); 29 | } 30 | 31 | return new FinalizedRequest($operation); 32 | } 33 | 34 | private function selectOperation(NormalizedRequest $normalizedRequest, ?string $operationName) : Operation 35 | { 36 | return $operationName === null 37 | ? $normalizedRequest->operations->getFirst() 38 | : $normalizedRequest->operations->offsetGet($operationName); 39 | } 40 | 41 | private function applyVariables(Operation $operation, \stdClass $variables) : void 42 | { 43 | $normalized = []; 44 | 45 | foreach ($operation->variables as $variable) { 46 | $this->path->add($variable->name . ' '); 47 | $value = $this->normalizeVariableValue($variable, $variables); 48 | 49 | foreach ($variable->directives as $directive) { 50 | $directiveDef = $directive->directive; 51 | \assert($directiveDef instanceof VariableDefinitionLocation); 52 | $directiveDef->resolveVariableDefinition($directive->arguments, $value); 53 | } 54 | 55 | $normalized[$variable->name] = $value; 56 | $this->path->pop(); 57 | } 58 | 59 | $operation->children->applyVariables(new VariableValueSet($normalized)); 60 | } 61 | 62 | private function normalizeVariableValue( 63 | Variable $variable, 64 | \stdClass $variables, 65 | ) : InputedValue 66 | { 67 | if (isset($variables->{$variable->name})) { 68 | return $variable->type->accept(new ConvertRawValueVisitor($variables->{$variable->name}, $this->path)); 69 | } 70 | 71 | if ($variable->defaultValue instanceof InputedValue) { 72 | return $variable->defaultValue; 73 | } 74 | 75 | return $variable->type->accept(new ConvertRawValueVisitor(null, $this->path)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Typesystem/Introspection/Schema.php: -------------------------------------------------------------------------------- 1 | getDescription(); 41 | }, 42 | ), 43 | ResolvableField::create( 44 | 'types', 45 | $this->container->getType('__Type')->notNullList(), 46 | static function (SchemaDef $schema) : array { 47 | return $schema->getContainer()->getTypes(true); 48 | }, 49 | ), 50 | ResolvableField::create( 51 | 'queryType', 52 | $this->container->getType('__Type')->notNull(), 53 | static function (SchemaDef $schema) : Type { 54 | return $schema->getQuery(); 55 | }, 56 | ), 57 | ResolvableField::create( 58 | 'mutationType', 59 | $this->container->getType('__Type'), 60 | static function (SchemaDef $schema) : ?Type { 61 | return $schema->getMutation(); 62 | }, 63 | ), 64 | ResolvableField::create( 65 | 'subscriptionType', 66 | $this->container->getType('__Type'), 67 | static function (SchemaDef $schema) : ?Type { 68 | return $schema->getSubscription(); 69 | }, 70 | ), 71 | ResolvableField::create( 72 | 'directives', 73 | $this->container->getType('__Directive')->notNullList(), 74 | static function (SchemaDef $schema) : array { 75 | return $schema->getContainer()->getDirectives(true); 76 | }, 77 | ), 78 | ]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Typesystem/Spec/DeprecatedDirective.php: -------------------------------------------------------------------------------- 1 | rawValue = self::coerceInput($type, $rawValue, $inputed); 22 | } 23 | 24 | #[\Override] 25 | public function getRawValue(bool $forResolvers = false) : string|\BackedEnum 26 | { 27 | return $this->rawValue; 28 | } 29 | 30 | #[\Override] 31 | public function getType() : EnumType 32 | { 33 | return $this->type; 34 | } 35 | 36 | #[\Override] 37 | public function jsonSerialize() : string 38 | { 39 | return self::coerceOutput($this->rawValue); 40 | } 41 | 42 | #[\Override] 43 | public function printValue() : string 44 | { 45 | return self::coerceOutput($this->rawValue); 46 | } 47 | 48 | #[\Override] 49 | public function applyVariables(VariableValueSet $variables) : void 50 | { 51 | // nothing here 52 | } 53 | 54 | #[\Override] 55 | public function resolveRemainingDirectives() : void 56 | { 57 | // nothing here 58 | } 59 | 60 | #[\Override] 61 | public function isSame(Value $compare) : bool 62 | { 63 | return $compare instanceof self 64 | && $this->rawValue === $compare->getRawValue(); 65 | } 66 | 67 | private static function coerceInput(EnumType $type, mixed $rawValue, bool $inputed) : string|\BackedEnum 68 | { 69 | $enumClass = $type->getEnumClass(); 70 | 71 | if (\is_string($rawValue) && $type->getItems()->offsetExists($rawValue)) { 72 | return \is_string($enumClass) 73 | ? \call_user_func([$enumClass, 'from'], $rawValue) // value should be a native enum 74 | : $rawValue; // value is correctly string 75 | } 76 | 77 | if ($rawValue instanceof \BackedEnum && $type->getItems()->offsetExists($rawValue->value)) { 78 | if (\is_string($enumClass)) { 79 | return $rawValue::class === $enumClass 80 | ? $rawValue // value is correctly native enum 81 | : \call_user_func([$enumClass, 'from'], $rawValue->value); // value is enum of different type -> convert 82 | } 83 | 84 | return $rawValue->value; // value should be string 85 | } 86 | 87 | throw new InvalidValue($type, $rawValue, $inputed); 88 | } 89 | 90 | private static function coerceOutput(string|\BackedEnum $rawValue) : string 91 | { 92 | return $rawValue instanceof \BackedEnum 93 | ? $rawValue->value 94 | : $rawValue; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Typesystem/Introspection/InputValue.php: -------------------------------------------------------------------------------- 1 | notNull(), 41 | static function (Argument $argument) : string { 42 | return $argument->getName(); 43 | }, 44 | ), 45 | ResolvableField::create( 46 | 'description', 47 | Container::String(), 48 | static function (Argument $argument) : ?string { 49 | return $argument->getDescription(); 50 | }, 51 | ), 52 | ResolvableField::create( 53 | 'type', 54 | $this->container->getType('__Type')->notNull(), 55 | static function (Argument $argument) : TypeContract { 56 | return $argument->getType(); 57 | }, 58 | ), 59 | ResolvableField::create( 60 | 'defaultValue', 61 | Container::String(), 62 | static function (Argument $argument) : ?string { 63 | return $argument->getDefaultValue() instanceof ArgumentValue 64 | ? $argument->getDefaultValue()->getValue()->printValue() 65 | : null; 66 | }, 67 | ), 68 | ResolvableField::create( 69 | 'isDeprecated', 70 | Container::Boolean()->notNull(), 71 | static function (Argument $argument) : bool { 72 | return $argument->isDeprecated(); 73 | }, 74 | ), 75 | ResolvableField::create( 76 | 'deprecationReason', 77 | Container::String(), 78 | static function (Argument $argument) : ?string { 79 | return $argument->getDeprecationReason(); 80 | }, 81 | ), 82 | ]); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Resolver/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | errorHandlingMode = $errorHandlingMode instanceof ErrorHandlingMode 20 | ? $errorHandlingMode 21 | : ErrorHandlingMode::fromBool($errorHandlingMode); 22 | } 23 | 24 | public function handle(\Throwable $exception) : Result 25 | { 26 | return match ($this->errorHandlingMode) { 27 | ErrorHandlingMode::ALL => self::handleAll($exception), 28 | ErrorHandlingMode::OUTPUTABLE => self::handleOutputable($exception), 29 | ErrorHandlingMode::CLIENT_AWARE => self::handleClientAware($exception), 30 | ErrorHandlingMode::NONE => self::handleNone($exception), 31 | }; 32 | } 33 | 34 | private static function handleAll(\Throwable $exception) : Result 35 | { 36 | return new Result(null, [ 37 | $exception instanceof ClientAware 38 | ? self::serializeError($exception) 39 | : self::notOutputableResponse(), 40 | ]); 41 | } 42 | 43 | private static function handleOutputable(\Throwable $exception) : Result 44 | { 45 | return $exception instanceof ClientAware && $exception->isOutputable() 46 | ? new Result(null, [self::serializeError($exception)]) 47 | : throw $exception; 48 | } 49 | 50 | private static function handleClientAware(\Throwable $exception) : Result 51 | { 52 | return $exception instanceof ClientAware 53 | ? new Result(null, [self::serializeError($exception)]) 54 | : throw $exception; 55 | } 56 | 57 | private static function handleNone(\Throwable $exception) : never 58 | { 59 | throw $exception; 60 | } 61 | 62 | private static function serializeError(ClientAware $exception) : array 63 | { 64 | if (!$exception->isOutputable()) { 65 | return self::notOutputableResponse(); 66 | } 67 | 68 | $result = [ 69 | 'message' => $exception->getMessage(), 70 | ]; 71 | 72 | if ($exception->getLocation() instanceof Location) { 73 | $result['locations'] = [$exception->getLocation()]; 74 | } 75 | 76 | if ($exception->getPath() instanceof Path) { 77 | $result['path'] = $exception->getPath(); 78 | } 79 | 80 | if (\is_array($exception->getExtensions())) { 81 | $result['extensions'] = $exception->getExtensions(); 82 | } 83 | 84 | return $result; 85 | } 86 | 87 | private static function notOutputableResponse() : array 88 | { 89 | return [ 90 | 'message' => 'Server responded with unknown error.', 91 | ]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Typesystem/Visitor/IsInstanceOfVisitor.php: -------------------------------------------------------------------------------- 1 | as it is a subset 24 | * - Int is NOT instanceof Int! 25 | * - UnionType is NOT instanceof UnionTypeItem 26 | * 27 | * @implements TypeVisitor 28 | */ 29 | final readonly class IsInstanceOfVisitor implements TypeVisitor 30 | { 31 | public function __construct( 32 | private TypeContract $typeToCompare, 33 | ) 34 | { 35 | } 36 | 37 | #[\Override] 38 | public function visitType(Type $type) : bool 39 | { 40 | return $type::class === $this->typeToCompare::class 41 | || ($this->typeToCompare instanceof AbstractType && $this->typeToCompare->accept(new IsImplementedByVisitor($type))); 42 | } 43 | 44 | #[\Override] 45 | public function visitInterface(InterfaceType $interface) : bool 46 | { 47 | return $interface::class === $this->typeToCompare::class 48 | || ($this->typeToCompare instanceof AbstractType && $this->typeToCompare->accept(new IsImplementedByVisitor($interface))); 49 | } 50 | 51 | #[\Override] 52 | public function visitUnion(UnionType $union) : bool 53 | { 54 | return $union::class === $this->typeToCompare::class; 55 | } 56 | 57 | #[\Override] 58 | public function visitInput(InputType $input) : bool 59 | { 60 | return $input::class === $this->typeToCompare::class; 61 | } 62 | 63 | #[\Override] 64 | public function visitScalar(ScalarType $scalar) : bool 65 | { 66 | return $scalar::class === $this->typeToCompare::class; 67 | } 68 | 69 | #[\Override] 70 | public function visitEnum(EnumType $enum) : bool 71 | { 72 | return $enum::class === $this->typeToCompare::class; 73 | } 74 | 75 | #[\Override] 76 | public function visitNotNull(NotNullType $notNull) : bool 77 | { 78 | return $this->typeToCompare instanceof NotNullType 79 | ? $notNull->getInnerType()->accept(new self($this->typeToCompare->getInnerType())) 80 | : $notNull->getInnerType()->accept($this); 81 | } 82 | 83 | #[\Override] 84 | public function visitList(ListType $list) : bool 85 | { 86 | return $this->typeToCompare instanceof ListType 87 | ? $list->getInnerType()->accept(new self($this->typeToCompare->getInnerType())) 88 | : false; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "infinityloop-dev/graphpinator", 3 | "description": "Easy-to-use & Fast GraphQL server implementation for PHP.", 4 | "homepage": "https://github.com/graphpql/", 5 | "type": "library", 6 | "license": ["MIT"], 7 | "authors": [ 8 | { 9 | "name": "Václav Pelíšek", 10 | "homepage": "https://www.peldax.com", 11 | "role": "lead" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=8.2", 16 | "ext-json": "*", 17 | "infinityloop-dev/graphpinator-common": "^2.0", 18 | "infinityloop-dev/graphpinator-parser": "^2.0", 19 | "infinityloop-dev/graphpinator-source": "^1.2", 20 | "infinityloop-dev/utils": "^2.3", 21 | "psr/http-message": "^2.0", 22 | "psr/log": "^3.0" 23 | }, 24 | "require-dev": { 25 | "guzzlehttp/psr7": "^2.8", 26 | "infection/infection": "^0.29", 27 | "infinityloop-dev/graphpinator-tokenizer": "^1.3", 28 | "phpstan/extension-installer": "^1.4", 29 | "phpstan/phpstan": "^2.0", 30 | "phpstan/phpstan-deprecation-rules": "^2.0", 31 | "phpstan/phpstan-strict-rules": "^2.0", 32 | "phpunit/phpunit": "^10.4", 33 | "shipmonk/composer-dependency-analyser": "^1.8", 34 | "thecodingmachine/phpstan-safe-rule": "^1.4", 35 | "webthinx/codestyle": "^1.1" 36 | }, 37 | "suggest": { 38 | "infinityloop-dev/graphpinator-nette": "Adapters for Nette framework.", 39 | "infinityloop-dev/graphpinator-printer": "Schema printing in GraphQL language.", 40 | "infinityloop-dev/graphpinator-constraint-directives": "Directives for additional value validation.", 41 | "infinityloop-dev/graphpinator-where-directives": "Directives for filtering list values.", 42 | "infinityloop-dev/graphpinator-extra-types": "Commonly used types, both scalar or composite.", 43 | "infinityloop-dev/graphpinator-upload": "Module to handle multipart formdata requests.", 44 | "infinityloop-dev/graphpinator-query-cost": "Modules to limit query cost by restricting maximum depth or number of nodes.", 45 | "infinityloop-dev/graphpinator-persisted-queries": "Module to persist validated query in cache and improve performace of repeating queries." 46 | }, 47 | "scripts": { 48 | "phpunit": "phpunit tests", 49 | "phpunit-no-coverage": "phpunit tests --no-coverage", 50 | "infection": [ 51 | "Composer\\Config::disableProcessTimeout", 52 | "infection -j$(nproc)" 53 | ], 54 | "phpstan": "phpstan analyze --level 4 src", 55 | "phpstan-next": "phpstan analyze --level 5 src", 56 | "phpstan-max": "phpstan analyze --level max src", 57 | "codestyle": "phpcs --extensions=php src tests", 58 | "codestyle-fix": "phpcbf --extensions=php src tests", 59 | "dependencies": "composer-dependency-analyser" 60 | }, 61 | "autoload": { 62 | "psr-4": { 63 | "Graphpinator\\": "src/" 64 | } 65 | }, 66 | "autoload-dev": { 67 | "psr-4": { 68 | "Graphpinator\\Tests\\": "tests/" 69 | } 70 | }, 71 | "config": { 72 | "allow-plugins": { 73 | "dealerdirect/phpcodesniffer-composer-installer": true, 74 | "infection/extension-installer": true, 75 | "phpstan/extension-installer": true 76 | }, 77 | "sort-packages": true 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Typesystem/EnumType.php: -------------------------------------------------------------------------------- 1 | directiveUsages = new DirectiveUsageSet(); 27 | } 28 | 29 | final public static function fromConstants() : EnumItemSet 30 | { 31 | $values = []; 32 | 33 | foreach ((new \ReflectionClass(static::class))->getReflectionConstants() as $constant) { 34 | $value = $constant->getValue(); 35 | 36 | if (!$constant->isPublic()) { 37 | continue; 38 | } 39 | 40 | $values[] = new EnumItem($value, self::getItemDescription($constant)); 41 | } 42 | 43 | return new EnumItemSet($values); 44 | } 45 | 46 | final public static function fromEnum(string $enumClass) : EnumItemSet 47 | { 48 | $values = []; 49 | $ref = new \ReflectionEnum($enumClass); 50 | 51 | if ((string) $ref->getBackingType() !== 'string') { 52 | throw new \InvalidArgumentException('Enum must be backed by string.'); 53 | } 54 | 55 | foreach ($ref->getCases() as $case) { 56 | \assert($case instanceof \ReflectionEnumBackedCase); 57 | 58 | $values[] = new EnumItem($case->getBackingValue(), self::getItemDescription($case)); 59 | } 60 | 61 | return new EnumItemSet($values, $enumClass); 62 | } 63 | 64 | final public function getItems() : EnumItemSet 65 | { 66 | return $this->options; 67 | } 68 | 69 | final public function getEnumClass() : ?string 70 | { 71 | return $this->options->getEnumClass(); 72 | } 73 | 74 | #[\Override] 75 | final public function accept(NamedTypeVisitor $visitor) : mixed 76 | { 77 | return $visitor->visitEnum($this); 78 | } 79 | 80 | /** 81 | * @param EnumLocation $directive 82 | * @phpcs:ignore 83 | * @param array $arguments 84 | */ 85 | final public function addDirective(EnumLocation $directive, array $arguments = []) : static 86 | { 87 | $this->directiveUsages[] = new DirectiveUsage($directive, $arguments); 88 | 89 | return $this; 90 | } 91 | 92 | private static function getItemDescription(\ReflectionClassConstant|\ReflectionEnumBackedCase $reflection) : ?string 93 | { 94 | $attrs = $reflection->getAttributes(Description::class); 95 | 96 | if (\count($attrs) === 1) { 97 | return $attrs[0]->newInstance()->getValue(); 98 | } 99 | 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Value/Visitor/CreateResolvedValueVisitor.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | final class CreateResolvedValueVisitor implements TypeVisitor 29 | { 30 | public function __construct( 31 | private mixed $rawValue, 32 | ) 33 | { 34 | } 35 | 36 | #[\Override] 37 | public function visitType(Type $type) : ResolvedValue 38 | { 39 | return $this->rawValue === null 40 | ? new NullValue($type) 41 | : new TypeIntermediateValue($type, $this->rawValue); 42 | } 43 | 44 | #[\Override] 45 | public function visitInterface(InterfaceType $interface) : ResolvedValue 46 | { 47 | return $this->rawValue === null 48 | ? new NullValue($interface) 49 | : $interface->createResolvedValue($this->rawValue); 50 | } 51 | 52 | #[\Override] 53 | public function visitUnion(UnionType $union) : ResolvedValue 54 | { 55 | return $this->rawValue === null 56 | ? new NullValue($union) 57 | : $union->createResolvedValue($this->rawValue); 58 | } 59 | 60 | #[\Override] 61 | public function visitInput(InputType $input) : never 62 | { 63 | throw new \LogicException(); // @codeCoverageIgnore 64 | } 65 | 66 | #[\Override] 67 | public function visitScalar(ScalarType $scalar) : ResolvedValue 68 | { 69 | return $this->rawValue === null 70 | ? new NullValue($scalar) 71 | : new ScalarValue($scalar, $this->rawValue, false); 72 | } 73 | 74 | #[\Override] 75 | public function visitEnum(EnumType $enum) : ResolvedValue 76 | { 77 | return $this->rawValue === null 78 | ? new NullValue($enum) 79 | : new EnumValue($enum, $this->rawValue, false); 80 | } 81 | 82 | #[\Override] 83 | public function visitNotNull(NotNullType $notNull) : ResolvedValue 84 | { 85 | return $this->rawValue === null 86 | ? throw new ValueCannotBeNull(false) 87 | : $notNull->getInnerType()->accept($this); 88 | } 89 | 90 | #[\Override] 91 | public function visitList(ListType $list) : ResolvedValue 92 | { 93 | if ($this->rawValue === null) { 94 | return new NullValue($list); 95 | } 96 | 97 | if (\is_iterable($this->rawValue)) { 98 | return new ListIntermediateValue($list, $this->rawValue); 99 | } 100 | 101 | throw new InvalidValue($list, $this->rawValue, false); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Resolver/Visitor/ResolveVisitor.php: -------------------------------------------------------------------------------- 1 | parentResult instanceof TypeIntermediateValue); 39 | \assert($this->selectionSet instanceof SelectionSet); 40 | 41 | foreach ($this->selectionSet as $selectionEntity) { 42 | $selectionEntity->accept(new ResolveSelectionVisitor($this->parentResult, $this->result)); 43 | } 44 | 45 | return new TypeValue($type, $this->result, $this->parentResult); 46 | } 47 | 48 | #[\Override] 49 | public function visitInterface(InterfaceType $interface) : never 50 | { 51 | throw new \LogicException(); // @codeCoverageIgnore 52 | } 53 | 54 | #[\Override] 55 | public function visitUnion(UnionType $union) : never 56 | { 57 | throw new \LogicException(); // @codeCoverageIgnore 58 | } 59 | 60 | #[\Override] 61 | public function visitInput(InputType $input) : never 62 | { 63 | throw new \LogicException(); // @codeCoverageIgnore 64 | } 65 | 66 | #[\Override] 67 | public function visitScalar(ScalarType $scalar) : ResolvedValue 68 | { 69 | return $this->parentResult; 70 | } 71 | 72 | #[\Override] 73 | public function visitEnum(EnumType $enum) : ResolvedValue 74 | { 75 | return $this->parentResult; 76 | } 77 | 78 | #[\Override] 79 | public function visitNotNull(NotNullType $notNull) : ResolvedValue 80 | { 81 | return $notNull->getInnerType()->accept($this); 82 | } 83 | 84 | #[\Override] 85 | public function visitList(ListType $list) : ListResolvedValue 86 | { 87 | \assert($this->parentResult instanceof ListIntermediateValue); 88 | 89 | $result = []; 90 | 91 | foreach ($this->parentResult->getRawValue() as $rawValue) { 92 | $value = $list->getInnerType()->accept(new CreateResolvedValueVisitor($rawValue)); 93 | $result[] = $value instanceof NullValue 94 | ? $value 95 | : $value->getType()->accept(new self($this->selectionSet, $value)); 96 | } 97 | 98 | return new ListResolvedValue($list, $result); 99 | } 100 | } 101 | --------------------------------------------------------------------------------