diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bcc678df..69e08514e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,13 @@ jobs: - php-version: '8.1' symfony-version: '7.4.*' include: + - php-version: '8.4' + symfony-version: '8.0.*' + dependencies: 'lowest' + - php-version: '8.4' + symfony-version: '8.0.*' + dependencies: 'highest' + coverage: 'pcov' - php-version: '8.2' symfony-version: '5.4.*' dependencies: 'lowest' diff --git a/composer.json b/composer.json index e2df667bf..d4c5338f5 100644 --- a/composer.json +++ b/composer.json @@ -38,15 +38,15 @@ "phpdocumentor/reflection-docblock": "^5.2", "phpdocumentor/type-resolver": "^1.6.1", "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/config": "^5.4.46 || ^6.4.32 || ^7.0", - "symfony/dependency-injection": "^5.4.48 || ^6.4.32 || ^7.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/expression-language": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/framework-bundle": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/http-foundation": "^5.4.50 || ^6.4.32 || ^7.0", - "symfony/http-kernel": "^5.4.50 || ^6.4.32 || ^7.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.30 || ^7.0", - "symfony/property-access": "^5.4.45 || ^6.4.32 || ^7.0", + "symfony/config": "^5.4.46 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/dependency-injection": "^5.4.48 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/event-dispatcher": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/expression-language": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/framework-bundle": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/http-foundation": "^5.4.50 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/http-kernel": "^5.4.50 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/options-resolver": "^5.4.45 || ^6.4.30 || ^7.0 || ^8.0", + "symfony/property-access": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", "webonyx/graphql-php": "^15.24" }, "suggest": { @@ -56,8 +56,8 @@ "symfony/translation": "If you want validation error messages to be translated." }, "require-dev": { - "doctrine/annotations": "^1.13", - "doctrine/orm": "^2.5", + "doctrine/annotations": "^1.14|^2.0", + "doctrine/orm": "^2.20.9 || ^3.6", "monolog/monolog": "^2.8.0 || ^3.0", "php-cs-fixer/shim": "^3.93", "phpstan/extension-installer": "^1.0", @@ -66,20 +66,20 @@ "phpstan/phpstan-symfony": "^1.0", "phpunit/phpunit": "^10.5.63", "react/promise": "^2.5", - "symfony/asset": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/browser-kit": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/console": "^5.4.47 || ^6.4.32 || ^7.0", - "symfony/css-selector": "^5.4.45 || ^6.4.24 || ^7.0", - "symfony/dom-crawler": "^5.4.48 || ^6.4.32 || ^7.0", - "symfony/finder": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/monolog-bundle": "^3.7", + "symfony/asset": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/browser-kit": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/console": "^5.4.47 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/css-selector": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^5.4.48 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/finder": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/monolog-bundle": "^3.7 || ^4.0", "symfony/phpunit-bridge": "^7.4.3", - "symfony/process": "^5.4.47 || ^6.4.32 || ^7.0", - "symfony/routing": "^5.4.48 || ^6.4.32 || ^7.0", - "symfony/security-bundle": "^5.4.45 || ^6.4.32 || ^7.0", - "symfony/validator": "^5.4.48 || ^6.4.31 || ^7.0", - "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.0", - "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.0", + "symfony/process": "^5.4.47 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/routing": "^5.4.48 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/security-bundle": "^5.4.45 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/validator": "^5.4.48 || ^6.4.31 || ^7.0 || ^8.0", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.0 || ^8.0", "twig/twig": "^2.10|^3.0" }, "conflict": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2a2ed6e0e..3805e48e1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -590,3 +590,22 @@ parameters: count: 1 path: tests/Relay/Connection/Output/DeprecatedPropertyPublicAccessTraitTest.php + - + message: "#^Call to function method_exists\\(\\) with '.*AnnotationRegistry' and 'registerLoader' will always evaluate to false\\.$#" + count: 1 + path: src/Config/Parser/AnnotationParser.php + + - + message: "#^Call to an undefined static method Doctrine\\\\Common\\\\Annotations\\\\AnnotationRegistry\\:\\:registerLoader\\(\\)\\.$#" + count: 1 + path: src/Config/Parser/AnnotationParser.php + + - + message: "#^Method Overblog\\\\GraphQLBundle\\\\Config\\\\Parser\\\\MetadataParser\\\\TypeGuesser\\\\DoctrineTypeGuesser\\:\\:getAnnotation\\(\\) has invalid return type Doctrine\\\\ORM\\\\Mapping\\\\Annotation\\.$#" + count: 1 + path: src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php + + - + message: "#^PHPDoc tag @var for variable \\$annotation contains unknown class Doctrine\\\\ORM\\\\Mapping\\\\Annotation\\.$#" + count: 1 + path: src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php diff --git a/src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php b/src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php index a65d18631..3d109184e 100644 --- a/src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php +++ b/src/Config/Parser/MetadataParser/TypeGuesser/DoctrineTypeGuesser.php @@ -35,7 +35,8 @@ public function getName(): string public function supports(Reflector $reflector): bool { - return $reflector instanceof ReflectionProperty; + // If we are on doctrine/orm v2 + return class_exists(\Doctrine\ORM\Version::class) && $reflector instanceof ReflectionProperty; } /** diff --git a/src/Generator/TypeBuilder.php b/src/Generator/TypeBuilder.php index 6efb9c88c..134119b20 100644 --- a/src/Generator/TypeBuilder.php +++ b/src/Generator/TypeBuilder.php @@ -32,6 +32,9 @@ use Overblog\GraphQLBundle\Generator\Converter\ExpressionConverter; use Overblog\GraphQLBundle\Generator\Exception\GeneratorException; use Overblog\GraphQLBundle\Validator\InputValidator; +use ReflectionClass; +use Symfony\Component\Validator\Constraints\Choice; +use Symfony\Component\Validator\Constraints\Video; use function array_map; use function class_exists; @@ -81,6 +84,7 @@ final class TypeBuilder private string $type; private string $currentField; private string $gqlServices = '$'.TypeGenerator::GRAPHQL_SERVICES; + private bool $isSymfony74Plus; public function __construct(ExpressionConverter $expressionConverter, string $namespace) { @@ -89,6 +93,7 @@ public function __construct(ExpressionConverter $expressionConverter, string $na // Register additional converter in the php code generator Config::registerConverter($expressionConverter, ConverterInterface::TYPE_STRING); + $this->isSymfony74Plus = class_exists(Video::class); } /** @@ -635,7 +640,32 @@ private function buildConstraints(array $constraints = [], bool $inClosure = tru } if (is_array($args)) { - if (isset($args[0]) && is_array($args[0])) { + if ($this->isSymfony74Plus && isset($args[0]) && Choice::class === $fqcn) { + // Handle Choice constraint in Symfony 7.4+ + $args = ['choices' => $args]; + } + + /* + * In Symfony 7.4+, we should not pass an array, but split up parameters in different arguments. + */ + if ($this->isSymfony74Plus && false === isset($args[0]) && [] !== $args) { + $reflectionClass = new ReflectionClass($fqcn); + $constructor = $reflectionClass->getConstructor(); + if (null === $constructor) { + throw new GeneratorException("Constraint '$fqcn' doesn't have a constructor."); + } + $parameters = $constructor->getParameters(); + foreach ($parameters as $parameter) { + $name = $parameter->getName(); + if (isset($args[$name])) { + $instance->addArgument($args[$name]); + } elseif ($parameter->isDefaultValueAvailable()) { + $instance->addArgument($parameter->getDefaultValue()); + } else { + throw new GeneratorException("Constraint '$fqcn' requires argument '$name'."); + } + } + } elseif (isset($args[0]) && is_array($args[0])) { // Nested instance $instance->addArgument($this->buildConstraints($args, false)); } elseif (isset($args['constraints'][0]) && is_array($args['constraints'][0])) { diff --git a/tests/Config/Parser/TestMetadataParser.php b/tests/Config/Parser/TestMetadataParser.php index f0032dca6..5a5e88184 100644 --- a/tests/Config/Parser/TestMetadataParser.php +++ b/tests/Config/Parser/TestMetadataParser.php @@ -90,7 +90,7 @@ public static function isDoctrineAnnotationInstalled(): bool public static function isDoctrineOrmInstalled(): bool { - return class_exists(Column::class); + return class_exists(Column::class) && class_exists(\Doctrine\ORM\Version::class); } protected function expect(string $name, string $type, array $config = []): void