From f8706497469560eab0492f4483655a670e6f6619 Mon Sep 17 00:00:00 2001 From: murtukov Date: Fri, 30 Oct 2020 18:04:47 +0100 Subject: [PATCH 1/4] Refactor TypeBuilder and resolve some todos - Add extended PHPDocs with code examples - Disable PHP-CS-Fixer rule to add unnecessary dots - Create static method to check expression prefix --- .php_cs.dist | 1 + .../ExpressionFunction/Security/Helper.php | 2 +- src/ExpressionLanguage/ExpressionLanguage.php | 18 +- .../Converter/ExpressionConverter.php | 7 +- src/Generator/TypeBuilder.php | 414 ++++++++++++++---- 5 files changed, 349 insertions(+), 93 deletions(-) diff --git a/.php_cs.dist b/.php_cs.dist index 0869149d9..b50b93074 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -17,6 +17,7 @@ return PhpCsFixer\Config::create() 'phpdoc_to_comment' => false, 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true], 'global_namespace_import' => ['import_functions' => true, 'import_classes' => true, 'import_constants' => true], + 'phpdoc_summary' => false, ] ) ->setFinder($finder) diff --git a/src/ExpressionLanguage/ExpressionFunction/Security/Helper.php b/src/ExpressionLanguage/ExpressionFunction/Security/Helper.php index 7f92beb57..16c0eed30 100644 --- a/src/ExpressionLanguage/ExpressionFunction/Security/Helper.php +++ b/src/ExpressionLanguage/ExpressionFunction/Security/Helper.php @@ -11,7 +11,7 @@ use function is_object; /** - * @deprecated since 0.13 will be remove in 0.14 + * @deprecated since 0.13 will be remove in 1.0 * @codeCoverageIgnore */ final class Helper diff --git a/src/ExpressionLanguage/ExpressionLanguage.php b/src/ExpressionLanguage/ExpressionLanguage.php index 5ac76ace1..b843524e8 100644 --- a/src/ExpressionLanguage/ExpressionLanguage.php +++ b/src/ExpressionLanguage/ExpressionLanguage.php @@ -44,8 +44,8 @@ public function compile($expression, $names = []) * Argument can be either an Expression object or a string with or * without a prefix * - * @param string $name - Name of the searched variable - * @param string|Expression $expression - Expression to search in + * @param string $name - name of the searched variable (needle) + * @param string|Expression $expression - expression to search in (haystack) * * @throws SyntaxError */ @@ -78,6 +78,20 @@ public static function expressionContainsVar(string $name, $expression): bool return $contained ?? false; } + /** + * Checks if value is a string and has the expression trigger prefix. + * + * @param mixed $value + */ + public static function isStringWithTrigger($value): bool + { + if (is_string($value)) { + return self::stringHasTrigger($value); + } + + return false; + } + /** * Checks if a string has the expression trigger prefix. */ diff --git a/src/Generator/Converter/ExpressionConverter.php b/src/Generator/Converter/ExpressionConverter.php index 1c0d92a61..33e98f0de 100644 --- a/src/Generator/Converter/ExpressionConverter.php +++ b/src/Generator/Converter/ExpressionConverter.php @@ -6,7 +6,6 @@ use Murtukov\PHPCodeGenerator\ConverterInterface; use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage; -use function is_string; class ExpressionConverter implements ConverterInterface { @@ -35,10 +34,6 @@ public function convert($value) */ public function check($maybeExpression): bool { - if (is_string($maybeExpression)) { - return ExpressionLanguage::stringHasTrigger($maybeExpression); - } - - return false; + return ExpressionLanguage::isStringWithTrigger($maybeExpression); } } diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php index c95df27ee..43f068d7b 100644 --- a/src/Generator/TypeBuilder.php +++ b/src/Generator/TypeBuilder.php @@ -29,11 +29,10 @@ use Overblog\GraphQLBundle\Definition\Type\CustomScalarType; use Overblog\GraphQLBundle\Definition\Type\GeneratedTypeInterface; use Overblog\GraphQLBundle\Error\ResolveErrors; -use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage; +use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionLanguage as EL; use Overblog\GraphQLBundle\Generator\Converter\ExpressionConverter; use Overblog\GraphQLBundle\Generator\Exception\GeneratorException; use Overblog\GraphQLBundle\Validator\InputValidator; -use RuntimeException; use function array_filter; use function array_intersect; use function array_map; @@ -57,13 +56,19 @@ use function trim; /** - * TODO (murtukov): - * 1. Add docblocks for every method - * 2. Replace hard-coded string types with constants ('object', 'input-object' etc.). + * Service that exposes a single method `build` called for each GraphQL + * type config to build a PhpFile object. + * + * {@link https://github.com/murtukov/php-code-generator} + * + * It's responsible for building all GraphQL types (object, input-object, + * interface, union, enum and custom-scalar). + * + * Every method with prefix 'build' has a render example in it's PHPDoc. */ class TypeBuilder { - protected const CONSTRAINTS_NAMESPACE = "Symfony\Component\Validator\Constraints"; + protected const CONSTRAINTS_NAMESPACE = 'Symfony\Component\Validator\Constraints'; protected const DOCBLOCK_TEXT = 'THIS FILE WAS GENERATED AND SHOULD NOT BE EDITED MANUALLY.'; protected const BUILT_IN_TYPES = [Type::STRING, Type::INT, Type::FLOAT, Type::BOOLEAN, Type::ID]; @@ -113,6 +118,7 @@ public function __construct(ExpressionConverter $expressionConverter, string $na */ public function build(array $config, string $type): PhpFile { + // This values should be accessible from every method $this->config = $config; $this->type = $type; @@ -140,9 +146,17 @@ public function build(array $config, string $type): PhpFile } /** - * @return GeneratorInterface|string + * Converts a native GraphQL type string into the `webonyx/graphql-php` + * type literal. + * + * Render examples: * - * @throws RuntimeException + * - "String" -> Type::string() + * - "String!" -> Type::nonNull(Type::string()) + * - "[String!] -> Type::listOf(Type::nonNull(Type::string())) + * - "[Post]" -> Type::listOf($globalVariables->get('typeResolver')->resolve('Post')) + * + * @return GeneratorInterface|string */ protected function buildType(string $typeDefinition) { @@ -152,11 +166,11 @@ protected function buildType(string $typeDefinition) } /** + * Used by {@see buildType}. + * * @param mixed $typeNode * * @return DependencyAwareGenerator|string - * - * @throws RuntimeException */ protected function wrapTypeRecursive($typeNode) { @@ -187,12 +201,93 @@ protected function wrapTypeRecursive($typeNode) } /** + * Builds an arrow function with an array as the return value. Content of + * the array depends on the GraphQL type that is currently being generated. + * + * Render example (object): + * + * fn() => [ + * 'name' => self::NAME, + * 'description' => 'Root query tyoe', + * 'fields' => fn() => [ + * 'posts' => {@see buildField}, + * 'users' => {@see buildField}, + * ... + * ], + * 'interfaces' => fn() => [ + * $globalVariables->get('typeResolver')->resolve('PostInterface'), + * ... + * ], + * 'resolveField' => {@see buildResolveField}, + * ] + * + * Render example (input-object): + * + * fn() => [ + * 'name' => self::NAME, + * 'description' => 'Some description.', + * 'validation' => {@see buildValidationRules} + * 'fields' => fn() => [ + * {@see buildField}, + * ... + * ], + * ] + * + * Render example (interface) + * + * fn() => [ + * 'name' => self::NAME, + * 'description' => 'Some description.', + * 'fields' => fn() => [ + * {@see buildField}, + * ... + * ], + * 'resolveType' => {@see buildResolveType}, + * ] + * + * Render example (union): + * + * fn() => [ + * 'name' => self::NAME, + * 'description' => 'Some description.', + * 'types' => fn() => [ + * $globalVariables->get('typeResolver')->resolve('Photo'), + * ... + * ], + * 'resolveType' => {@see buildResolveType}, + * ] + * + * Render example (custom-scalar): + * + * fn() => [ + * 'name' => self::NAME, + * 'description' => 'Some description' + * 'serialize' => {@see buildScalarCallback}, + * 'parseValue' => {@see buildScalarCallback}, + * 'parseLiteral' => {@see buildScalarCallback}, + * ] + * + * Render example (enum): + * + * fn() => [ + * 'name' => self::NAME, + * 'values' => [ + * 'PUBLISHED' => ['value' => 1], + * 'DRAFT' => ['value' => 2], + * 'STANDBY' => [ + * 'value' => 3, + * 'description' => 'Waiting for validation', + * ], + * ... + * ], + * ] + * * @throws GeneratorException * @throws UnrecognizedValueTypeException */ protected function buildConfigLoader(array $config): ArrowFunction { - // Convert to object for better readability + // Convert to an object for a better readability $c = (object) $config; $configLoader = Collection::assoc(); @@ -202,11 +297,12 @@ protected function buildConfigLoader(array $config): ArrowFunction $configLoader->addItem('description', $c->description); } - // only by InputType (class level validation) + // only by input-object types (for class level validation) if (isset($c->validation)) { $configLoader->addItem('validation', $this->buildValidationRules($c->validation)); } + // only by object, input-object and interface types if (!empty($c->fields)) { $configLoader->addItem('fields', ArrowFunction::new( Collection::map($c->fields, [$this, 'buildField']) @@ -231,10 +327,12 @@ protected function buildConfigLoader(array $config): ArrowFunction $configLoader->addItem('resolveField', $this->buildResolve($c->resolveField)); } + // only by enum types if (isset($c->values)) { $configLoader->addItem('values', Collection::assoc($c->values)); } + // only by custom-scalar types if ('custom-scalar' === $this->type) { if (isset($c->scalarType)) { $configLoader->addItem('scalarType', $c->scalarType); @@ -257,11 +355,17 @@ protected function buildConfigLoader(array $config): ArrowFunction } /** - * @param callable $callback + * Builds an arrow function that calls a static method. * - * @return ArrowFunction + * Render example: + * + * fn() => MyClassName::myMethodName(...\func_get_args()) + * + * @param callable $callback - a callable string or a callable array * * @throws GeneratorException + * + * @return ArrowFunction */ protected function buildScalarCallback($callback, string $fieldName) { @@ -280,7 +384,7 @@ protected function buildScalarCallback($callback, string $fieldName) $className = Utils::resolveQualifier($class); if ($className === $this->config['class_name']) { - // Create alias if name of serializer is same as type name + // Create an alias if name of serializer is same as type name $className = 'Base'.$className; $this->file->addUse($class, $className); } else { @@ -293,12 +397,46 @@ protected function buildScalarCallback($callback, string $fieldName) } /** - * @param mixed $resolve + * Builds a resolver closure that contains the compiled result of user-defined + * expression and optionally the validation logic. * - * @return GeneratorInterface + * Render example (no expression language): + * + * function ($value, $args, $context, $info) use ($globalVariables) { + * return "Hello, World!"; + * } + * + * Render example (with expression language): + * + * function ($value, $args, $context, $info) use ($globalVariables) { + * return $globalVariables->get('mutationResolver')->resolve(["my_resolver", [0 => $args]]); + * } + * + * Render example (with validation): + * + * function ($value, $args, $context, $info) use ($globalVariables) { + * $validator = {@see buildValidatorInstance} + * return $globalVariables->get('mutationResolver')->resolve(["create_post", [0 => $validator]]); + * } + * + * Render example (with validation, but errors are injected into the user-defined resolver): + * {@link https://github.com/overblog/GraphQLBundle/blob/master/docs/validation/index.md#injecting-errors} + * + * function ($value, $args, $context, $info) use ($globalVariables) { + * $errors = new ResolveErrors(); + * $validator = {@see buildValidatorInstance} + * + * $errors->setValidationErrors($validator->validate(null, false)) + * + * return $globalVariables->get('mutationResolver')->resolve(["create_post", [0 => $errors]]); + * } + * + * @param mixed $resolve * * @throws GeneratorException * @throws UnrecognizedValueTypeException + * + * @return GeneratorInterface */ protected function buildResolve($resolve, ?array $validationConfig = null) { @@ -310,18 +448,36 @@ protected function buildResolve($resolve, ?array $validationConfig = null) ->addArguments('value', 'args', 'context', 'info') ->bindVar(TypeGenerator::GLOBAL_VARS); - // TODO (murtukov): replace usage of converter with ExpressionLanguage static method - if ($this->expressionConverter->check($resolve)) { - $injectErrors = ExpressionLanguage::expressionContainsVar('errors', $resolve); + if (EL::isStringWithTrigger($resolve)) { + $injectErrors = EL::expressionContainsVar('errors', $resolve); if ($injectErrors) { $closure->append('$errors = ', Instance::new(ResolveErrors::class)); } - $injectValidator = ExpressionLanguage::expressionContainsVar('validator', $resolve); + $injectValidator = EL::expressionContainsVar('validator', $resolve); if (null !== $validationConfig) { - $this->buildValidator($closure, $validationConfig, $injectValidator, $injectErrors); + $closure->append('$validator = ', $this->buildValidatorInstance($validationConfig)); + + // If auto-validation on or errors are injected + if (!$injectValidator || $injectErrors) { + if (!empty($validationConfig['validationGroups'])) { + $validationGroups = Collection::numeric($validationConfig['validationGroups']); + } else { + $validationGroups = 'null'; + } + + $closure->emptyLine(); + + if ($injectErrors) { + $closure->append('$errors->setValidationErrors($validator->validate(', $validationGroups, ', false))'); + } else { + $closure->append('$validator->validate(', $validationGroups, ')'); + } + + $closure->emptyLine(); + } } elseif (true === $injectValidator) { throw new GeneratorException( 'Unable to inject an instance of the InputValidator. No validation constraints provided. '. @@ -340,7 +496,20 @@ protected function buildResolve($resolve, ?array $validationConfig = null) return $closure; } - protected function buildValidator(Closure $closure, array $mapping, bool $injectValidator, bool $injectErrors): void + /** + * Render example: + * + * new InputValidator( + * \func_get_args(), + * $globalVariables->get('container')->get('validator'), + * $globalVariables->get('validatorFactory'), + * {@see buildProperties}, + * {@see buildValidationRules}, + * ) + * + * @throws GeneratorException + */ + protected function buildValidatorInstance(array $mapping): Instance { $validator = Instance::new(InputValidator::class) ->setMultiline() @@ -356,29 +525,20 @@ protected function buildValidator(Closure $closure, array $mapping, bool $inject $validator->addArgument($this->buildValidationRules($mapping['class'])); } - $closure->append('$validator = ', $validator); - - // If auto-validation on or errors are injected - if (!$injectValidator || $injectErrors) { - if (!empty($mapping['validationGroups'])) { - $validationGroups = Collection::numeric($mapping['validationGroups']); - } else { - $validationGroups = 'null'; - } - - $closure->emptyLine(); - - if ($injectErrors) { - $closure->append('$errors->setValidationErrors($validator->validate(', $validationGroups, ', false))'); - } else { - $closure->append('$validator->validate(', $validationGroups, ')'); - } - - $closure->emptyLine(); - } + return $validator; } /** + * Render example: + * + * [ + * 'link' => {@see normalizeLink} + * 'cascade' => {@see buildCascade}, + * 'constraints' => {@see buildConstraints} + * ] + * + * If only constraints provided, build {@see buildConstraints} directly. + * * @param array{ * constraints: array, * link: string, @@ -396,7 +556,7 @@ protected function buildValidationRules(array $config): Collection if (!empty($c->link)) { if (false === strpos($c->link, '::')) { - // e.g.: App\Entity\Droid + // e.g. App\Entity\Droid $array->addItem('link', $c->link); } else { // e.g. App\Entity\Droid::$id @@ -420,13 +580,19 @@ protected function buildValidationRules(array $config): Collection } /** - * - * [ - * new NotNull(), - * new Length(['min' => 5, 'max' => 10]), - * ... - * ] - * . + * Builds a numeric multiline array with Symfony Constraint instances. + * The array is used by {@see InputValidator} during requests. + * + * Render example: + * + * [ + * new NotNull(), + * new Length([ + * 'min' => 5, + * 'max' => 10 + * ]), + * ... + * ] * * @throws GeneratorException */ @@ -474,6 +640,19 @@ protected function buildConstraints(array $constraints = []): Collection } /** + * Builds an assoc multiline array with a predefined shape. The array + * is used by {@see InputValidator} during requests. + * + * Possible keys are: 'groups', 'isCollection' and 'referenceType'. + * + * Render example: + * + * [ + * 'groups' => ['my_group'], + * 'isCollection' => true, + * 'referenceType' => $globalVariables->get('typeResolver')->resolve('Article') + * ] + * * @param array{ * referenceType: string, * groups: array, @@ -486,11 +665,11 @@ protected function buildCascade(array $cascade): Collection { $c = (object) $cascade; - $result = Collection::assoc() + $array = Collection::assoc() ->addIfNotEmpty('groups', $c->groups); if (isset($c->isCollection)) { - $result->addItem('isCollection', $c->isCollection); + $array->addItem('isCollection', $c->isCollection); } if (isset($c->referenceType)) { @@ -500,12 +679,23 @@ protected function buildCascade(array $cascade): Collection throw new GeneratorException('Cascade validation cannot be applied to built-in types.'); } - $result->addItem('referenceType', "$this->globalVars->get('typeResolver')->resolve('$c->referenceType')"); + $array->addItem('referenceType', "$this->globalVars->get('typeResolver')->resolve('$c->referenceType')"); } - return $result; // @phpstan-ignore-line + return $array; // @phpstan-ignore-line } + /** + * Render example: + * + * [ + * 'firstName' => {@see buildValidationRules}, + * 'lastName' => {@see buildValidationRules}, + * ... + * ] + * + * @throws GeneratorException + */ protected function buildProperties(array $properties): Collection { $array = Collection::assoc(); @@ -518,6 +708,21 @@ protected function buildProperties(array $properties): Collection } /** + * Render example: + * + * [ + * 'type' => {@see buildType}, + * 'description' => 'Some description.', + * 'deprecationReason' => 'This field will be removed soon.', + * 'args' => fn() => [ + * {@see buildArg}, + * {@see buildArg}, + * ... + * ], + * 'resolve' => {@see buildResolve}, + * 'complexity' => {@see buildComplexity}, + * ] + * * @param array{ * type: string, * resolve?: string, @@ -528,10 +733,12 @@ protected function buildProperties(array $properties): Collection * validation?: array, * } $fieldConfig * - * @return GeneratorInterface|Collection|string + * @internal * * @throws GeneratorException * @throws UnrecognizedValueTypeException + * + * @return GeneratorInterface|Collection|string */ public function buildField(array $fieldConfig /*, $fieldname */) { @@ -576,19 +783,35 @@ public function buildField(array $fieldConfig /*, $fieldname */) $field->addItem('access', $this->buildAccess($c->access)); } - if (!empty($c->access) && is_string($c->access) && ExpressionLanguage::expressionContainsVar('object', $c->access)) { + if (!empty($c->access) && is_string($c->access) && EL::expressionContainsVar('object', $c->access)) { $field->addItem('useStrictAccess', false); } if ('input-object' === $this->type && isset($c->validation)) { - $this->restructureInputValidationConfig($fieldConfig); - $field->addItem('validation', $this->buildValidationRules($fieldConfig['validation'])); + // restructure validation config + if (!empty($c->validation['cascade'])) { + $c->validation['cascade']['isCollection'] = $this->isCollectionType($c->type); + $c->validation['cascade']['referenceType'] = trim($c->type, '[]!'); + } + + $field->addItem('validation', $this->buildValidationRules($c->validation)); } return $field; } /** + * Render example: + * + * [ + * 'name' => 'username', + * 'type' => {@see buildType}, + * 'description' => 'Some fancy description.', + * 'defaultValue' => 'admin', + * ] + * + * @internal + * * @param array{ * type: string, * description?: string, @@ -616,16 +839,29 @@ public function buildArg(array $argConfig, string $argName): Collection } /** + * Builds a closure or an arrow function, depending on whether the `args` param is provided. + * + * Render example (closure): + * + * function ($value, $arguments) use ($globalVariables) { + * $args = $globalVariables->get('argumentFactory')->create($arguments); + * return ($args['age'] + 5); + * } + * + * Render example (arrow function): + * + * fn($childrenComplexity) => ($childrenComplexity + 20); + * * @param mixed $complexity * * @return Closure|mixed */ protected function buildComplexity($complexity) { - if ($this->expressionConverter->check($complexity)) { + if (EL::isStringWithTrigger($complexity)) { $expression = $this->expressionConverter->convert($complexity); - if (ExpressionLanguage::expressionContainsVar('args', $complexity)) { + if (EL::expressionContainsVar('args', $complexity)) { return Closure::new() ->addArgument('childrenComplexity') ->addArgument('arguments', '', []) @@ -637,7 +873,7 @@ protected function buildComplexity($complexity) $arrow = ArrowFunction::new(is_string($expression) ? new Literal($expression) : $expression); - if (ExpressionLanguage::expressionContainsVar('childrenComplexity', $complexity)) { + if (EL::expressionContainsVar('childrenComplexity', $complexity)) { $arrow->addArgument('childrenComplexity'); } @@ -648,21 +884,28 @@ protected function buildComplexity($complexity) } /** + * Builds an arrow function from a string with an expression prefix, + * otherwise just returns the provided value back untouched. + * + * Render example (if expression): + * + * fn($fieldName, $typeName = self::NAME) => ($fieldName == "name") + * * @param mixed $public * * @return ArrowFunction|mixed */ protected function buildPublic($public) { - if ($this->expressionConverter->check($public)) { + if (EL::isStringWithTrigger($public)) { $expression = $this->expressionConverter->convert($public); $arrow = ArrowFunction::new(Literal::new($expression)); - if (ExpressionLanguage::expressionContainsVar('fieldName', $public)) { + if (EL::expressionContainsVar('fieldName', $public)) { $arrow->addArgument('fieldName'); } - if (ExpressionLanguage::expressionContainsVar('typeName', $public)) { + if (EL::expressionContainsVar('typeName', $public)) { $arrow->addArgument('fieldName'); $arrow->addArgument('typeName', '', new Literal('self::NAME')); } @@ -674,13 +917,20 @@ protected function buildPublic($public) } /** + * Builds an arrow function from a string with an expression prefix, + * otherwise just returns the provided value back untouched. + * + * Render example (if expression): + * + * fn($value, $args, $context, $info, $object) => $globalVariables->get('private_service')->hasAccess() + * * @param mixed $access * * @return ArrowFunction|mixed */ protected function buildAccess($access) { - if ($this->expressionConverter->check($access)) { + if (EL::isStringWithTrigger($access)) { $expression = $this->expressionConverter->convert($access); return ArrowFunction::new() @@ -692,13 +942,20 @@ protected function buildAccess($access) } /** + * Builds an arrow function from a string with an expression prefix, + * otherwise just returns the provided value back untouched. + * + * Render example: + * + * fn($value, $context, $info) => $globalVariables->get('typeResolver')->resolve($value) + * * @param mixed $resolveType * * @return mixed|ArrowFunction */ protected function buildResolveType($resolveType) { - if ($this->expressionConverter->check($resolveType)) { + if (EL::isStringWithTrigger($resolveType)) { $expression = $this->expressionConverter->convert($resolveType); return ArrowFunction::new() @@ -709,17 +966,6 @@ protected function buildResolveType($resolveType) return $resolveType; } - // TODO (murtukov): rework this method to use builders - protected function restructureInputValidationConfig(array &$fieldConfig): void - { - if (empty($fieldConfig['validation']['cascade'])) { - return; - } - - $fieldConfig['validation']['cascade']['isCollection'] = $this->isCollectionType($fieldConfig['type']); - $fieldConfig['validation']['cascade']['referenceType'] = trim($fieldConfig['type'], '[]!'); - } - // TODO (murtukov): rework this method to use builders protected function restructureObjectValidationConfig(array $fieldConfig): ?array { @@ -776,13 +1022,13 @@ protected function isCollectionType(string $type): bool } /** - * Creates and array from a formatted string, e.g.:. + * Creates and array from a formatted string. + * + * Examples: * - * ``` - * "App\Entity\User::$firstName" -> ['App\Entity\User', 'firstName', 'property'] - * "App\Entity\User::firstName()" -> ['App\Entity\User', 'firstName', 'getter'] - * "App\Entity\User::firstName" -> ['App\Entity\User', 'firstName', 'member'] - * ```. + * "App\Entity\User::$firstName" -> ['App\Entity\User', 'firstName', 'property'] + * "App\Entity\User::firstName()" -> ['App\Entity\User', 'firstName', 'getter'] + * "App\Entity\User::firstName" -> ['App\Entity\User', 'firstName', 'member'] */ protected function normalizeLink(string $link): array { From 08d871198aa769eff70eb0099ec3ddd959eab576 Mon Sep 17 00:00:00 2001 From: murtukov Date: Sat, 31 Oct 2020 16:11:27 +0100 Subject: [PATCH 2/4] Fix typos --- src/Generator/TypeBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php index 43f068d7b..415445389 100644 --- a/src/Generator/TypeBuilder.php +++ b/src/Generator/TypeBuilder.php @@ -208,7 +208,7 @@ protected function wrapTypeRecursive($typeNode) * * fn() => [ * 'name' => self::NAME, - * 'description' => 'Root query tyoe', + * 'description' => 'Root query type', * 'fields' => fn() => [ * 'posts' => {@see buildField}, * 'users' => {@see buildField}, @@ -537,7 +537,7 @@ protected function buildValidatorInstance(array $mapping): Instance * 'constraints' => {@see buildConstraints} * ] * - * If only constraints provided, build {@see buildConstraints} directly. + * If only constraints provided, uses {@see buildConstraints} directly. * * @param array{ * constraints: array, From 19bae10e972b3a4a14b309b0354efa56604a8ff0 Mon Sep 17 00:00:00 2001 From: murtukov Date: Mon, 2 Nov 2020 16:20:51 +0100 Subject: [PATCH 3/4] Use arrow function without arguments if no expression is used --- src/Generator/TypeBuilder.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php index 415445389..f7b1848f8 100644 --- a/src/Generator/TypeBuilder.php +++ b/src/Generator/TypeBuilder.php @@ -436,7 +436,7 @@ protected function buildScalarCallback($callback, string $fieldName) * @throws GeneratorException * @throws UnrecognizedValueTypeException * - * @return GeneratorInterface + * @return GeneratorInterface|string */ protected function buildResolve($resolve, ?array $validationConfig = null) { @@ -444,11 +444,11 @@ protected function buildResolve($resolve, ?array $validationConfig = null) return Collection::numeric($resolve); } - $closure = Closure::new() - ->addArguments('value', 'args', 'context', 'info') - ->bindVar(TypeGenerator::GLOBAL_VARS); - if (EL::isStringWithTrigger($resolve)) { + $closure = Closure::new() + ->addArguments('value', 'args', 'context', 'info') + ->bindVar(TypeGenerator::GLOBAL_VARS); + $injectErrors = EL::expressionContainsVar('errors', $resolve); if ($injectErrors) { @@ -491,9 +491,7 @@ protected function buildResolve($resolve, ?array $validationConfig = null) return $closure; } - $closure->append('return ', Utils::stringify($resolve)); - - return $closure; + return ArrowFunction::new($resolve); } /** From fbf08461cf76f805ccccfbc59e7700cc1b92c373 Mon Sep 17 00:00:00 2001 From: murtukov Date: Mon, 2 Nov 2020 18:22:14 +0100 Subject: [PATCH 4/4] Fix phpstan errors introduced in 0.12.53 --- src/Error/InvalidArgumentError.php | 9 +++++++-- src/Error/InvalidArgumentsError.php | 8 ++++++-- src/Error/UserErrors.php | 8 ++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Error/InvalidArgumentError.php b/src/Error/InvalidArgumentError.php index a4f5debfc..f247b5586 100644 --- a/src/Error/InvalidArgumentError.php +++ b/src/Error/InvalidArgumentError.php @@ -13,8 +13,13 @@ class InvalidArgumentError extends GraphQLUserError private string $name; private ConstraintViolationListInterface $errors; - public function __construct(string $name, ConstraintViolationListInterface $errors, $message = '', $code = 0, Exception $previous = null) - { + public function __construct( + string $name, + ConstraintViolationListInterface $errors, + string $message = '', + int $code = 0, + Exception $previous = null + ) { $this->name = $name; $this->errors = $errors; parent::__construct($message, $code, $previous); diff --git a/src/Error/InvalidArgumentsError.php b/src/Error/InvalidArgumentsError.php index 07024a880..9dd2ae09e 100644 --- a/src/Error/InvalidArgumentsError.php +++ b/src/Error/InvalidArgumentsError.php @@ -12,8 +12,12 @@ class InvalidArgumentsError extends GraphQLUserError /** @var InvalidArgumentError[] */ private array $errors; - public function __construct(array $errors, $message = '', $code = 0, Exception $previous = null) - { + public function __construct( + array $errors, + string $message = '', + int $code = 0, + Exception $previous = null + ) { $this->errors = $errors; parent::__construct($message, $code, $previous); } diff --git a/src/Error/UserErrors.php b/src/Error/UserErrors.php index 44b782990..edc8fb9ec 100644 --- a/src/Error/UserErrors.php +++ b/src/Error/UserErrors.php @@ -16,8 +16,12 @@ class UserErrors extends RuntimeException /** @var UserError[] */ private array $errors = []; - public function __construct(array $errors, $message = '', $code = 0, Exception $previous = null) - { + public function __construct( + array $errors, + string $message = '', + int $code = 0, + Exception $previous = null + ) { $this->setErrors($errors); parent::__construct($message, $code, $previous); }