Skip to content

Adding Type generator #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ControllerQueryProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ private function getSourceFields(): array
$typeField = $this->annotationReader->getClassAnnotation($refClass, \TheCodingMachine\GraphQL\Controllers\Annotations\Type::class);

if ($typeField === null) {
throw MissingAnnotationException::missingTypeException();
throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
}

$objectClass = $typeField->getClass();
Expand Down
10 changes: 8 additions & 2 deletions src/Mappers/GlobTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Psr\SimpleCache\CacheInterface;
use TheCodingMachine\ClassExplorer\Glob\GlobClassExplorer;
use TheCodingMachine\GraphQL\Controllers\Annotations\Type;
use TheCodingMachine\GraphQL\Controllers\TypeGenerator;
use Youshido\GraphQL\Type\InputTypeInterface;
use Youshido\GraphQL\Type\TypeInterface;

Expand Down Expand Up @@ -44,17 +45,22 @@ final class GlobTypeMapper implements TypeMapperInterface
* @var ContainerInterface
*/
private $container;
/**
* @var TypeGenerator
*/
private $typeGenerator;

/**
* @param string $namespace The namespace that contains the GraphQL types (they must have a `@Type` annotation)
*/
public function __construct(string $namespace, ContainerInterface $container, Reader $annotationReader, CacheInterface $cache, ?int $cacheTtl = null)
public function __construct(string $namespace, TypeGenerator $typeGenerator, ContainerInterface $container, Reader $annotationReader, CacheInterface $cache, ?int $cacheTtl = null)
{
$this->namespace = $namespace;
$this->container = $container;
$this->annotationReader = $annotationReader;
$this->cache = $cache;
$this->cacheTtl = $cacheTtl;
$this->typeGenerator = $typeGenerator;
}

/**
Expand Down Expand Up @@ -126,7 +132,7 @@ public function mapClassToType(string $className): TypeInterface
if (!isset($map[$className])) {
throw CannotMapTypeException::createForType($className);
}
return $this->container->get($map[$className]);
return $this->typeGenerator->mapAnnotatedObject($this->container->get($map[$className]));
}

/**
Expand Down
9 changes: 7 additions & 2 deletions src/MissingAnnotationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

class MissingAnnotationException extends \RuntimeException
{
public static function missingTypeException(): self
public static function missingTypeExceptionToUseSourceField(): self
{
return new self('You cannot use the @SourceField annotation without also adding a @Type annotation.');
}
}

public static function missingTypeException(): self
{
return new self('GraphQL type classes must provide a @Type annotation.');
}
}
79 changes: 79 additions & 0 deletions src/TypeGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers;

use ReflectionClass;
use TheCodingMachine\GraphQL\Controllers\Annotations\Type;
use TheCodingMachine\GraphQL\Controllers\Registry\RegistryInterface;
use Youshido\GraphQL\Type\Object\ObjectType;
use Youshido\GraphQL\Type\TypeInterface;

/**
* This class is in charge of creating Youshido GraphQL types from annotated objects that do not extend the
* Youshido base class.
*/
class TypeGenerator
{
/**
* @var RegistryInterface
*/
private $registry;

public function __construct(RegistryInterface $registry)
{
$this->registry = $registry;
}

/**
* @param object $annotatedObject An object with a @Type annotation.
* @return TypeInterface
*/
public function mapAnnotatedObject($annotatedObject): TypeInterface
{
// Objects that are already a GraphQL type need no attention.
if ($annotatedObject instanceof TypeInterface) {
return $annotatedObject;
}

$refTypeClass = new \ReflectionClass($annotatedObject);

/** @var \TheCodingMachine\GraphQL\Controllers\Annotations\Type|null $typeField */
$typeField = $this->registry->getAnnotationReader()->getClassAnnotation($refTypeClass, \TheCodingMachine\GraphQL\Controllers\Annotations\Type::class);

if ($typeField === null) {
throw MissingAnnotationException::missingTypeException();
}



$fieldProvider = new ControllerQueryProvider($annotatedObject, $this->registry);
$fields = $fieldProvider->getFields();

$type = new ObjectType([
'name' => $this->getName($refTypeClass, $typeField),
'fields' => $fields,
]);

return $type;
}

private function getName(ReflectionClass $refTypeClass, Type $type): string
{
$className = $refTypeClass->getName();

if ($prevPos = strrpos($className, '\\')) {
$className = substr($className, $prevPos + 1);
}
// By default, if the class name ends with Type, let's take the name of the class for the type
if (substr($className, -4) === 'Type') {
return substr($className, 0, -4);
}
// Else, let's take the name of the targeted class
$className = $type->getClass();
if ($prevPos = strrpos($className, '\\')) {
$className = substr($className, $prevPos + 1);
}
return $className;
}
}
18 changes: 18 additions & 0 deletions tests/Fixtures/TypeFoo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Fixtures;

use TheCodingMachine\GraphQL\Controllers\Annotations\SourceField;
use TheCodingMachine\GraphQL\Controllers\Annotations\Type;

/**
* A type whose class name does not END with Type
*
* @Type(class=TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject::class)
* @SourceField(name="test")
*/
class TypeFoo
{

}
6 changes: 3 additions & 3 deletions tests/Fixtures/Types/AbstractFooType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
use Youshido\GraphQL\Config\Object\ObjectTypeConfig;
use Youshido\GraphQL\Type\Object\AbstractObjectType;

abstract class AbstractFooType extends AbstractAnnotatedObjectType
abstract class AbstractFooType /*extends AbstractAnnotatedObjectType*/
{
public function __construct(RegistryInterface $registry)
/*public function __construct(RegistryInterface $registry)
{
parent::__construct($registry);
}
}*/

/**
* @Field()
Expand Down
16 changes: 12 additions & 4 deletions tests/Mappers/GlobTypeMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestObject;
use TheCodingMachine\GraphQL\Controllers\Fixtures\TestType;
use TheCodingMachine\GraphQL\Controllers\Fixtures\Types\FooType;
use TheCodingMachine\GraphQL\Controllers\TypeGenerator;
use Youshido\GraphQL\Type\Object\ObjectType;

class GlobTypeMapperTest extends AbstractQueryProviderTest
{
Expand All @@ -20,10 +22,12 @@ public function testGlobTypeMapper()
}
]);

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures\Types', $container, new AnnotationReader(), new NullCache());
$typeGenerator = new TypeGenerator($this->getRegistry());

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures\Types', $typeGenerator, $container, new AnnotationReader(), new NullCache());

$this->assertTrue($mapper->canMapClassToType(TestObject::class));
$this->assertInstanceOf(FooType::class, $mapper->mapClassToType(TestObject::class));
$this->assertInstanceOf(ObjectType::class, $mapper->mapClassToType(TestObject::class));

$this->expectException(CannotMapTypeException::class);
$mapper->mapClassToType(\stdClass::class);
Expand All @@ -37,7 +41,9 @@ public function testGlobTypeMapperException()
}
]);

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures', $container, new AnnotationReader(), new NullCache());
$typeGenerator = new TypeGenerator($this->getRegistry());

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures', $typeGenerator, $container, new AnnotationReader(), new NullCache());

$this->expectException(DuplicateMappingException::class);
$mapper->canMapClassToType(TestType::class);
Expand All @@ -51,7 +57,9 @@ public function testGlobTypeMapperInputType()
}
]);

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures\Types', $container, new AnnotationReader(), new NullCache());
$typeGenerator = new TypeGenerator($this->getRegistry());

$mapper = new GlobTypeMapper('TheCodingMachine\GraphQL\Controllers\Fixtures\Types', $typeGenerator, $container, new AnnotationReader(), new NullCache());

$this->assertFalse($mapper->canMapClassToInputType(TestObject::class));

Expand Down
35 changes: 35 additions & 0 deletions tests/TypeGeneratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace TheCodingMachine\GraphQL\Controllers;

use TheCodingMachine\GraphQL\Controllers\Fixtures\TypeFoo;
use Youshido\GraphQL\Type\Object\ObjectType;

class TypeGeneratorTest extends AbstractQueryProviderTest
{
public function testIdentityWhenMappingAlreadyAType()
{
$typeGenerator = new TypeGenerator($this->getRegistry());

$type = new ObjectType(['name'=>'foo']);
$this->assertSame($type, $typeGenerator->mapAnnotatedObject($type));
}

public function testNameAndFields()
{
$typeGenerator = new TypeGenerator($this->getRegistry());

$type = $typeGenerator->mapAnnotatedObject(new TypeFoo());

$this->assertSame('TestObject', $type->getName());
$this->assertCount(1, $type->getFields());
}

public function testMapAnnotatedObjectException()
{
$typeGenerator = new TypeGenerator($this->getRegistry());

$this->expectException(MissingAnnotationException::class);
$typeGenerator->mapAnnotatedObject(new \stdClass());
}
}