diff --git a/.travis.yml b/.travis.yml
index dbd477bf64..9ceadd1daf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,13 @@ jobs:
- composer phpstan
after_script:
- ./vendor/bin/coveralls -v
+ - stage: test
+ php: 7.4snapshot
+ env: PREFER_LOWEST=""
+ before_script:
+ - *composerupdate
+ script:
+ - *phpunit
- stage: test
php: 7.2
env: PREFER_LOWEST=""
@@ -91,6 +98,5 @@ jobs:
- git config --global user.email "${GH_EMAIL}"
- echo "machine github.com login ${GH_NAME} password ${GH_TOKEN}" > ~/.netrc
- cd website && yarn install && GIT_USER="${GH_NAME}" yarn run publish-gh-pages
-matrix:
allow_failures:
- stage: test_dependencies
diff --git a/composer.json b/composer.json
index f4ac263941..bd17eacb01 100644
--- a/composer.json
+++ b/composer.json
@@ -17,14 +17,14 @@
"doctrine/cache": "^1.8",
"thecodingmachine/class-explorer": "^1.0.2",
"psr/simple-cache": "^1",
- "phpdocumentor/reflection-docblock": "^4.3",
- "phpdocumentor/type-resolver": "^0.4",
+ "phpdocumentor/reflection-docblock": "^4.3 | ^5.0",
+ "phpdocumentor/type-resolver": "^0.4 || ^1.0.0",
"psr/http-message": "^1",
"ecodev/graphql-upload": "^4.0",
"symfony/lock": "^3 || ^4"
},
"require-dev": {
- "phpunit/phpunit": "^6.1",
+ "phpunit/phpunit": "^7.5.16",
"satooshi/php-coveralls": "^1.0",
"symfony/cache": "^4.1.4",
"mouf/picotainer": "^1.1",
@@ -49,7 +49,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "3.0.x-dev"
+ "dev-master": "3.1.x-dev"
}
}
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 6b8eec52ef..c7b363d680 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -8,13 +8,13 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
- syntaxCheck="false"
- bootstrap="vendor/autoload.php"
+ bootstrap="tests/Bootstrap.php"
>
./tests/
./tests/dependencies/
+ ./tests/Bootstrap.php
@@ -24,7 +24,7 @@
-
+
diff --git a/src/FieldsBuilder.php b/src/FieldsBuilder.php
index 80c839c8bc..50c81eb439 100644
--- a/src/FieldsBuilder.php
+++ b/src/FieldsBuilder.php
@@ -233,7 +233,7 @@ private function getFieldsByAnnotations($controller, string $annotationName, boo
}
}
- foreach ($refClass->getMethods() as $refMethod) {
+ foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
if ($closestMatchingTypeClass !== null && $closestMatchingTypeClass === $refMethod->getDeclaringClass()->getName()) {
// Optimisation: no need to fetch annotations from parent classes that are ALREADY GraphQL types.
// We will merge the fields anyway.
@@ -308,7 +308,9 @@ private function mapReturnType(ReflectionMethod $refMethod, DocBlock $docBlockOb
$returnType = $refMethod->getReturnType();
if ($returnType !== null) {
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
- $phpdocType = $typeResolver->resolve((string) $returnType);
+ $phpdocType = $typeResolver->resolve(
+ $returnType->getName()
+ );
$phpdocType = $this->resolveSelf($phpdocType, $refMethod->getDeclaringClass());
} else {
$phpdocType = new Mixed_();
@@ -490,10 +492,11 @@ private function mapParameters(array $refParameters, DocBlock $docBlock): array
$parameterType = $parameter->getType();
$allowsNull = $parameterType === null ? true : $parameterType->allowsNull();
- $type = (string) $parameterType;
- if ($type === '') {
+ if ($parameterType === null) {
throw MissingTypeHintException::missingTypeHint($parameter);
}
+
+ $type = $parameterType->getName();
$phpdocType = $typeResolver->resolve($type);
$phpdocType = $this->resolveSelf($phpdocType, $parameter->getDeclaringClass());
diff --git a/src/GlobControllerQueryProvider.php b/src/GlobControllerQueryProvider.php
index b999251a66..a679bf5f90 100644
--- a/src/GlobControllerQueryProvider.php
+++ b/src/GlobControllerQueryProvider.php
@@ -39,6 +39,10 @@ final class GlobControllerQueryProvider implements QueryProviderInterface
* @var ContainerInterface
*/
private $container;
+ /**
+ * @var ClassNameMapper
+ */
+ private $classNameMapper;
/**
* @var AggregateControllerQueryProvider
*/
@@ -69,10 +73,11 @@ final class GlobControllerQueryProvider implements QueryProviderInterface
* @param int|null $cacheTtl
* @param bool $recursive Whether subnamespaces of $namespace must be analyzed.
*/
- public function __construct(string $namespace, FieldsBuilderFactory $fieldsBuilderFactory, RecursiveTypeMapperInterface $recursiveTypeMapper, ContainerInterface $container, LockFactory $lockFactory, CacheInterface $cache, ?int $cacheTtl = null, bool $recursive = true)
+ public function __construct(string $namespace, FieldsBuilderFactory $fieldsBuilderFactory, RecursiveTypeMapperInterface $recursiveTypeMapper, ContainerInterface $container, LockFactory $lockFactory, CacheInterface $cache, ?ClassNameMapper $classNameMapper = null, ?int $cacheTtl = null, bool $recursive = true)
{
$this->namespace = $namespace;
$this->container = $container;
+ $this->classNameMapper = $classNameMapper ?? ClassNameMapper::createFromComposerFile(null, null, true);
$this->cache = $cache;
$this->cacheTtl = $cacheTtl;
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
@@ -126,7 +131,7 @@ private function getInstancesList(): array
*/
private function buildInstancesList(): array
{
- $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->cacheTtl, ClassNameMapper::createFromComposerFile(null, null, true), $this->recursive);
+ $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->cacheTtl, $this->classNameMapper, $this->recursive);
$classes = $explorer->getClasses();
$instances = [];
foreach ($classes as $className) {
diff --git a/src/InputTypeUtils.php b/src/InputTypeUtils.php
index 8e3eb02104..a24a3b9571 100644
--- a/src/InputTypeUtils.php
+++ b/src/InputTypeUtils.php
@@ -55,7 +55,7 @@ private function validateReturnType(ReflectionMethod $refMethod): Fqsen
throw MissingTypeHintException::nullableReturnType($refMethod);
}
- $type = (string) $returnType;
+ $type = $returnType->getName();
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
diff --git a/src/Mappers/GlobTypeMapper.php b/src/Mappers/GlobTypeMapper.php
index 82f0709fb2..e76c2d999b 100644
--- a/src/Mappers/GlobTypeMapper.php
+++ b/src/Mappers/GlobTypeMapper.php
@@ -126,15 +126,20 @@ final class GlobTypeMapper implements TypeMapperInterface
* @var LockFactory
*/
private $lockFactory;
+ /**
+ * @var ClassNameMapper
+ */
+ private $classNameMapper;
/**
* @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
*/
- public function __construct(string $namespace, TypeGenerator $typeGenerator, InputTypeGenerator $inputTypeGenerator, InputTypeUtils $inputTypeUtils, ContainerInterface $container, AnnotationReader $annotationReader, NamingStrategyInterface $namingStrategy, LockFactory $lockFactory, CacheInterface $cache, ?int $globTtl = 2, ?int $mapTtl = null, bool $recursive = true)
+ public function __construct(string $namespace, TypeGenerator $typeGenerator, InputTypeGenerator $inputTypeGenerator, InputTypeUtils $inputTypeUtils, ContainerInterface $container, AnnotationReader $annotationReader, NamingStrategyInterface $namingStrategy, LockFactory $lockFactory, CacheInterface $cache, ClassNameMapper $classNameMapper = null, ?int $globTtl = 2, ?int $mapTtl = null, bool $recursive = true)
{
$this->namespace = $namespace;
$this->typeGenerator = $typeGenerator;
$this->container = $container;
+ $this->classNameMapper = $classNameMapper ?? ClassNameMapper::createFromComposerFile(null, null, true);
$this->annotationReader = $annotationReader;
$this->namingStrategy = $namingStrategy;
$this->cache = $cache;
@@ -288,7 +293,7 @@ private function getClassList(): array
{
if ($this->classes === null) {
$this->classes = [];
- $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->globTtl, ClassNameMapper::createFromComposerFile(null, null, true), $this->recursive);
+ $explorer = new GlobClassExplorer($this->namespace, $this->cache, $this->globTtl, $this->classNameMapper, $this->recursive);
$classes = $explorer->getClasses();
foreach ($classes as $className) {
if (!\class_exists($className)) {
@@ -329,8 +334,8 @@ private function buildMap(): void
$isAbstract = $refClass->isAbstract();
- foreach ($refClass->getMethods() as $method) {
- if (!$method->isPublic() || ($isAbstract && !$method->isStatic())) {
+ foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
+ if ($isAbstract && !$method->isStatic()) {
continue;
}
$factory = $this->annotationReader->getFactoryAnnotation($method);
diff --git a/src/MissingTypeHintException.php b/src/MissingTypeHintException.php
index 162e27fe68..418557cf10 100644
--- a/src/MissingTypeHintException.php
+++ b/src/MissingTypeHintException.php
@@ -18,7 +18,7 @@ public static function missingReturnType(ReflectionMethod $method): self
public static function invalidReturnType(ReflectionMethod $method): self
{
- return new self(sprintf('The return type of factory "%s::%s" must be an object, "%s" passed instead.', $method->getDeclaringClass()->getName(), $method->getName(), $method->getReturnType()));
+ return new self(sprintf('The return type of factory "%s::%s" must be an object, "%s" passed instead.', $method->getDeclaringClass()->getName(), $method->getName(), $method->getReturnType() ? $method->getReturnType()->getName() : 'mixed'));
}
public static function nullableReturnType(ReflectionMethod $method): self
diff --git a/src/QueryField.php b/src/QueryField.php
index 2fd93a8c01..4b5944abc1 100644
--- a/src/QueryField.php
+++ b/src/QueryField.php
@@ -45,7 +45,7 @@ public function __construct(string $name, OutputType $type, array $arguments, ?c
$config = [
'name' => $name,
'type' => $type,
- 'args' => array_map(function(array $item) { return $item['type']; }, $arguments)
+ 'args' => $arguments,
];
if ($comment) {
$config['description'] = $comment;
diff --git a/src/SchemaFactory.php b/src/SchemaFactory.php
index 0e33105224..adbfb96529 100644
--- a/src/SchemaFactory.php
+++ b/src/SchemaFactory.php
@@ -10,6 +10,7 @@
use Doctrine\Common\Cache\ApcuCache;
use function extension_loaded;
use GraphQL\Type\SchemaConfig;
+use Mouf\Composer\ClassNameMapper;
use Psr\Container\ContainerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Lock\Factory as LockFactory;
@@ -74,6 +75,10 @@ class SchemaFactory
* @var ContainerInterface
*/
private $container;
+ /**
+ * @var ClassNameMapper
+ */
+ private $classNameMapper;
/**
* @var SchemaConfig
*/
@@ -180,6 +185,12 @@ public function setSchemaConfig(SchemaConfig $schemaConfig): self
return $this;
}
+ public function setClassNameMapper(ClassNameMapper $classNameMapper): self
+ {
+ $this->classNameMapper = $classNameMapper;
+ return $this;
+ }
+
public function createSchema(): Schema
{
$annotationReader = new AnnotationReader($this->getDoctrineAnnotationReader(), AnnotationReader::LAX_MODE);
@@ -210,7 +221,7 @@ public function createSchema(): Schema
foreach ($this->typeNamespaces as $typeNamespace) {
$typeMappers[] = new GlobTypeMapper($typeNamespace, $typeGenerator, $inputTypeGenerator, $inputTypeUtils,
- $this->container, $annotationReader, $namingStrategy, $lockFactory, $this->cache);
+ $this->container, $annotationReader, $namingStrategy, $lockFactory, $this->cache, $this->classNameMapper);
}
foreach ($this->typeMappers as $typeMapper) {
@@ -229,7 +240,7 @@ public function createSchema(): Schema
$queryProviders = [];
foreach ($this->controllerNamespaces as $controllerNamespace) {
$queryProviders[] = new GlobControllerQueryProvider($controllerNamespace, $fieldsBuilderFactory, $recursiveTypeMapper,
- $this->container, $lockFactory, $this->cache);
+ $this->container, $lockFactory, $this->cache, $this->classNameMapper);
}
foreach ($this->queryProviders as $queryProvider) {
diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php
new file mode 100644
index 0000000000..2ff4503963
--- /dev/null
+++ b/tests/Bootstrap.php
@@ -0,0 +1,9 @@
+toArray(Debug::RETHROW_INTERNAL_EXCEPTIONS)['data']);
}
+ public function testDefaultValueInSchema()
+ {
+ /** @var Schema $schema */
+ $schema = $this->mainContainer->get(Schema::class);
+
+ $queryString = '
+ query deprecatedField {
+ __type(name: "Query") {
+ fields {
+ name
+ args {
+ name
+ defaultValue
+ }
+ }
+ }
+ }
+ ';
+
+ $result = GraphQL::executeQuery(
+ $schema,
+ $queryString
+ );
+
+ $defaultField = null;
+ foreach ($result->data['__type']['fields'] as $field) {
+ if ($field['name'] === 'defaultValue') {
+ $defaultField = $field;
+ break;
+ }
+ }
+
+ $this->assertSame(
+ $defaultField['args'][0]['defaultValue'],
+ '"value"'
+ );
+ }
+
}
diff --git a/tests/Mappers/GlobTypeMapperTest.php b/tests/Mappers/GlobTypeMapperTest.php
index 95b27eaed8..422474ed8a 100644
--- a/tests/Mappers/GlobTypeMapperTest.php
+++ b/tests/Mappers/GlobTypeMapperTest.php
@@ -79,9 +79,19 @@ public function testGlobTypeMapperDuplicateInputTypesException()
$mapper = new GlobTypeMapper('TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes', $typeGenerator, $this->getInputTypeGenerator(), $this->getInputTypeUtils(), $container, new \TheCodingMachine\GraphQLite\AnnotationReader(new AnnotationReader()), new NamingStrategy(), $this->getLockFactory(), new NullCache());
- $this->expectException(DuplicateMappingException::class);
- $this->expectExceptionMessage('The class \'TheCodingMachine\GraphQLite\Fixtures\TestObject\' should be mapped to only one GraphQL Input type. Two methods are pointing via the @Factory annotation to this class: \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory::myFactory\' and \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory2::myFactory\'');
- $mapper->canMapClassToInputType(TestObject::class);
+ $caught = false;
+ try {
+ $mapper->canMapClassToInputType(TestObject::class);
+ } catch (DuplicateMappingException $e) {
+ // Depending on the environment, one of the messages can be returned.
+ $this->assertContains($e->getMessage(),
+ [
+ 'The class \'TheCodingMachine\GraphQLite\Fixtures\TestObject\' should be mapped to only one GraphQL Input type. Two methods are pointing via the @Factory annotation to this class: \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory::myFactory\' and \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory2::myFactory\'',
+ 'The class \'TheCodingMachine\GraphQLite\Fixtures\TestObject\' should be mapped to only one GraphQL Input type. Two methods are pointing via the @Factory annotation to this class: \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory2::myFactory\' and \'TheCodingMachine\GraphQLite\Fixtures\DuplicateInputTypes\TestFactory::myFactory\''
+ ]);
+ $caught = true;
+ }
+ $this->assertTrue($caught, 'DuplicateMappingException is thrown');
}
public function testGlobTypeMapperClassNotFoundException()
diff --git a/tests/SchemaFactoryTest.php b/tests/SchemaFactoryTest.php
index e9e3907d07..ec7460279f 100644
--- a/tests/SchemaFactoryTest.php
+++ b/tests/SchemaFactoryTest.php
@@ -5,11 +5,14 @@
use GraphQL\Error\Debug;
use GraphQL\GraphQL;
use GraphQL\Type\SchemaConfig;
+use Mouf\Composer\ClassNameMapper;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Cache\Simple\ArrayCache;
use Symfony\Component\Cache\Simple\PhpFilesCache;
use TheCodingMachine\GraphQLite\Containers\BasicAutoWiringContainer;
use TheCodingMachine\GraphQLite\Containers\EmptyContainer;
use TheCodingMachine\GraphQLite\Hydrators\FactoryHydrator;
+use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper;
use TheCodingMachine\GraphQLite\Security\VoidAuthenticationService;
use TheCodingMachine\GraphQLite\Security\VoidAuthorizationService;
@@ -57,10 +60,47 @@ public function testSetters(): void
$this->doTestSchema($schema);
}
+ public function testClassNameMapperInjectionWithValidMapper(): void
+ {
+ $factory = new SchemaFactory(
+ new ArrayCache(),
+ new BasicAutoWiringContainer(
+ new EmptyContainer()
+ )
+ );
+ $factory->setAuthenticationService(new VoidAuthenticationService())
+ ->setAuthorizationService(new VoidAuthorizationService())
+ ->setClassNameMapper(ClassNameMapper::createFromComposerFile(null, null, true))
+ ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers')
+ ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration');
+
+ $schema = $factory->createSchema();
+
+ $this->doTestSchema($schema);
+ }
+
+ public function testClassNameMapperInjectionWithInvalidMapper(): void
+ {
+ $factory = new SchemaFactory(
+ new ArrayCache(),
+ new BasicAutoWiringContainer(
+ new EmptyContainer()
+ )
+ );
+ $factory->setAuthenticationService(new VoidAuthenticationService())
+ ->setAuthorizationService(new VoidAuthorizationService())
+ ->setClassNameMapper(new ClassNameMapper())
+ ->addControllerNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration\\Controllers')
+ ->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration');
+
+ $this->expectException(\TypeError::class);
+ $this->doTestSchema($factory->createSchema());
+ }
+
public function testException(): void
{
$container = new BasicAutoWiringContainer(new EmptyContainer());
- $cache = new PhpFilesCache();
+ $cache = new ArrayCache();
$factory = new SchemaFactory($cache, $container);
@@ -71,7 +111,7 @@ public function testException(): void
public function testException2(): void
{
$container = new BasicAutoWiringContainer(new EmptyContainer());
- $cache = new PhpFilesCache();
+ $cache = new ArrayCache();
$factory = new SchemaFactory($cache, $container);
$factory->addTypeNamespace('TheCodingMachine\\GraphQLite\\Fixtures\\Integration');