mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 10:17:39 +08:00
Fixed bug that GraphQL cannot work. (#6894)
This commit is contained in:
parent
be50ab279f
commit
52898c9004
1
.github/workflows/test-components.yml
vendored
1
.github/workflows/test-components.yml
vendored
@ -317,6 +317,7 @@ jobs:
|
||||
- name: Run Test Cases
|
||||
run: |
|
||||
./.travis/requirement.install.sh
|
||||
composer remove thecodingmachine/graphqlite --dev -W
|
||||
composer require "psr/simple-cache:${{ matrix.psr-version }}"
|
||||
composer update -oW
|
||||
composer analyse src/cache
|
||||
|
@ -91,6 +91,7 @@
|
||||
"symfony/serializer": "^5.0 || ^6.0",
|
||||
"symfony/uid": "^5.0 || ^6.0",
|
||||
"symfony/var-dumper": "^5.0 || ^6.0",
|
||||
"thecodingmachine/graphqlite": "^7.0",
|
||||
"twig/twig": "^3.0",
|
||||
"vlucas/phpdotenv": "^5.0",
|
||||
"zircote/swagger-php": "^4.6"
|
||||
|
@ -24,7 +24,6 @@ parameters:
|
||||
- %currentWorkingDirectory%/src/database-pgsql/*
|
||||
- %currentWorkingDirectory%/src/db/src/PgSQL/*
|
||||
- %currentWorkingDirectory%/src/filesystem/*
|
||||
- %currentWorkingDirectory%/src/graphql/*
|
||||
- %currentWorkingDirectory%/src/grpc/*
|
||||
- %currentWorkingDirectory%/src/grpc-server/*
|
||||
- %currentWorkingDirectory%/src/ide-helper/*
|
||||
@ -84,4 +83,4 @@ parameters:
|
||||
- message: '#Method .* should return .* but returns#'
|
||||
path: src/collection/src/*.php
|
||||
- message: '#has no type specified#'
|
||||
path: src/nacos/src/Protobuf/*.php
|
||||
path: src/nacos/src/Protobuf/*.php
|
||||
|
@ -15,7 +15,7 @@
|
||||
"hyperf/contract": "~3.1.0",
|
||||
"hyperf/di": "~3.1.0",
|
||||
"swow/psr7-plus": "^1.0",
|
||||
"thecodingmachine/graphqlite": "^3.0"
|
||||
"thecodingmachine/graphqlite": "^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"hyperf/http-message": "Required to use GraphQLMiddleware.",
|
||||
|
@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Hyperf\Di\Annotation\AnnotationCollector;
|
||||
use Hyperf\Di\ReflectionManager;
|
||||
use Hyperf\GraphQL\ClassCollector;
|
||||
use ReflectionProperty;
|
||||
|
||||
trait AnnotationTrait
|
||||
{
|
||||
public function toArray(): array
|
||||
{
|
||||
$properties = ReflectionManager::reflectClass(static::class)->getProperties(ReflectionProperty::IS_PUBLIC);
|
||||
$result = [];
|
||||
foreach ($properties as $property) {
|
||||
$result[$property->getName()] = $property->getValue($this);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function collectClass(string $className): void
|
||||
{
|
||||
AnnotationCollector::collectClass($className, static::class, $this);
|
||||
ClassCollector::collect($className);
|
||||
}
|
||||
|
||||
public function collectMethod(string $className, ?string $target): void
|
||||
{
|
||||
AnnotationCollector::collectMethod($className, $target, static::class, $this);
|
||||
ClassCollector::collect($className);
|
||||
}
|
||||
|
||||
public function collectProperty(string $className, ?string $target): void
|
||||
{
|
||||
AnnotationCollector::collectProperty($className, $target, static::class, $this);
|
||||
ClassCollector::collect($className);
|
||||
}
|
||||
|
||||
protected function bindMainProperty(string $key, array $value)
|
||||
{
|
||||
if (isset($value['value'])) {
|
||||
$this->{$key} = $value['value'];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
* @Attributes({
|
||||
* @Attribute("class", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class ExtendType extends \TheCodingMachine\GraphQLite\Annotations\ExtendType implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
* @Attributes({
|
||||
* @Attribute("name", type="string")
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Factory extends \TheCodingMachine\GraphQLite\Annotations\Factory implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class FailWith extends \TheCodingMachine\GraphQLite\Annotations\FailWith implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
* @Attributes({
|
||||
* @Attribute("outputType", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Field extends \TheCodingMachine\GraphQLite\Annotations\Field implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Logged extends \TheCodingMachine\GraphQLite\Annotations\Logged implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
* @Attributes({
|
||||
* @Attribute("outputType", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Mutation extends \TheCodingMachine\GraphQLite\Annotations\Mutation implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
* @Attributes({
|
||||
* @Attribute("outputType", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Query extends \TheCodingMachine\GraphQLite\Annotations\Query implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"ANNOTATION", "METHOD"})
|
||||
* @Attributes({
|
||||
* @Attribute("name", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class Right extends \TheCodingMachine\GraphQLite\Annotations\Right implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
* @Attributes({
|
||||
* @Attribute("name", type="string"),
|
||||
* @Attribute("logged", type="bool"),
|
||||
* @Attribute("right", type="TheCodingMachine\GraphQLite\Annotations\Right"),
|
||||
* @Attribute("outputType", type="string"),
|
||||
* @Attribute("isId", type="bool"),
|
||||
* @Attribute("failWith", type="mixed"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class SourceField extends \TheCodingMachine\GraphQLite\Annotations\SourceField implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL\Annotation;
|
||||
|
||||
use Attribute;
|
||||
use Hyperf\Di\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
* @Attributes({
|
||||
* @Attribute("class", type="string"),
|
||||
* })
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class Type extends \TheCodingMachine\GraphQLite\Annotations\Type implements AnnotationInterface
|
||||
{
|
||||
use AnnotationTrait;
|
||||
}
|
@ -1,247 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Hyperf\GraphQL\Annotation\ExtendType;
|
||||
use Hyperf\GraphQL\Annotation\Factory;
|
||||
use Hyperf\GraphQL\Annotation\FailWith;
|
||||
use Hyperf\GraphQL\Annotation\Logged;
|
||||
use Hyperf\GraphQL\Annotation\Right;
|
||||
use Hyperf\GraphQL\Annotation\SourceField;
|
||||
use Hyperf\GraphQL\Annotation\Type;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
use TheCodingMachine\GraphQLite\Annotations\AbstractRequest;
|
||||
use TheCodingMachine\GraphQLite\Annotations\Exceptions\ClassNotFoundException;
|
||||
|
||||
use function array_filter;
|
||||
use function in_array;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
|
||||
class AnnotationReader
|
||||
{
|
||||
// In this mode, no exceptions will be thrown for incorrect annotations (unless the name of the annotation we are looking for is part of the docblock)
|
||||
public const LAX_MODE = 'LAX_MODE';
|
||||
|
||||
// In this mode, exceptions will be thrown for any incorrect annotations.
|
||||
public const STRICT_MODE = 'STRICT_MODE';
|
||||
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
private $reader;
|
||||
|
||||
/**
|
||||
* Classes in those namespaces MUST have valid annotations (otherwise, an error is thrown).
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $strictNamespaces;
|
||||
|
||||
/**
|
||||
* If true, no exceptions will be thrown for incorrect annotations in code coming from the "vendor/" directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $methodAnnotationCache = [];
|
||||
|
||||
/**
|
||||
* AnnotationReader constructor.
|
||||
* @param string $mode One of self::LAX_MODE or self::STRICT_MODE
|
||||
*/
|
||||
public function __construct(Reader $reader, string $mode = self::STRICT_MODE, array $strictNamespaces = [])
|
||||
{
|
||||
$this->reader = $reader;
|
||||
if (! in_array($mode, [self::LAX_MODE, self::STRICT_MODE], true)) {
|
||||
throw new InvalidArgumentException('The mode passed must be one of AnnotationReader::LAX_MODE, AnnotationReader::STRICT_MODE');
|
||||
}
|
||||
$this->mode = $mode;
|
||||
$this->strictNamespaces = $strictNamespaces;
|
||||
}
|
||||
|
||||
public function getTypeAnnotation(ReflectionClass $refClass): ?Type
|
||||
{
|
||||
try {
|
||||
/** @var null|Type $type */
|
||||
$type = $this->getClassAnnotation($refClass, Type::class);
|
||||
if ($type !== null && $type->isSelfType()) {
|
||||
$type->setClass($refClass->getName());
|
||||
}
|
||||
} catch (ClassNotFoundException $e) {
|
||||
throw ClassNotFoundException::wrapException($e, $refClass->getName());
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
public function getExtendTypeAnnotation(ReflectionClass $refClass): ?ExtendType
|
||||
{
|
||||
try {
|
||||
/** @var null|ExtendType $extendType */
|
||||
$extendType = $this->getClassAnnotation($refClass, ExtendType::class);
|
||||
} catch (ClassNotFoundException $e) {
|
||||
throw ClassNotFoundException::wrapExceptionForExtendTag($e, $refClass->getName());
|
||||
}
|
||||
return $extendType;
|
||||
}
|
||||
|
||||
public function getRequestAnnotation(ReflectionMethod $refMethod, string $annotationName): ?AbstractRequest
|
||||
{
|
||||
return $this->getMethodAnnotation($refMethod, $annotationName);
|
||||
}
|
||||
|
||||
public function getLoggedAnnotation(ReflectionMethod $refMethod): ?Logged
|
||||
{
|
||||
return $this->getMethodAnnotation($refMethod, Logged::class);
|
||||
}
|
||||
|
||||
public function getRightAnnotation(ReflectionMethod $refMethod): ?Right
|
||||
{
|
||||
return $this->getMethodAnnotation($refMethod, Right::class);
|
||||
}
|
||||
|
||||
public function getFailWithAnnotation(ReflectionMethod $refMethod): ?FailWith
|
||||
{
|
||||
return $this->getMethodAnnotation($refMethod, FailWith::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SourceField[]
|
||||
*/
|
||||
public function getSourceFields(ReflectionClass $refClass): array
|
||||
{
|
||||
return $this->getClassAnnotations($refClass, SourceField::class);
|
||||
}
|
||||
|
||||
public function getFactoryAnnotation(ReflectionMethod $refMethod): ?Factory
|
||||
{
|
||||
return $this->getMethodAnnotation($refMethod, Factory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class annotations. Finds in the parents too.
|
||||
*
|
||||
* @return object[]
|
||||
*/
|
||||
public function getClassAnnotations(ReflectionClass $refClass, string $annotationClass): array
|
||||
{
|
||||
$toAddAnnotations = [];
|
||||
do {
|
||||
try {
|
||||
$allAnnotations = $this->reader->getClassAnnotations($refClass);
|
||||
$toAddAnnotations[] = array_filter($allAnnotations, function ($annotation) use ($annotationClass): bool {
|
||||
return $annotation instanceof $annotationClass;
|
||||
});
|
||||
} catch (AnnotationException $e) {
|
||||
if ($this->mode === self::STRICT_MODE) {
|
||||
throw $e;
|
||||
}
|
||||
if ($this->mode === self::LAX_MODE) {
|
||||
if ($this->isErrorImportant($annotationClass, $refClass->getDocComment(), $refClass->getName())) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
$refClass = $refClass->getParentClass();
|
||||
} while ($refClass);
|
||||
|
||||
if (! empty($toAddAnnotations)) {
|
||||
return array_merge(...$toAddAnnotations);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a class annotation. Finds in the parents if not found in the main class.
|
||||
*
|
||||
* @return null|object
|
||||
*/
|
||||
private function getClassAnnotation(ReflectionClass $refClass, string $annotationClass)
|
||||
{
|
||||
do {
|
||||
$type = null;
|
||||
try {
|
||||
$type = $this->reader->getClassAnnotation($refClass, $annotationClass);
|
||||
} catch (AnnotationException $e) {
|
||||
switch ($this->mode) {
|
||||
case self::STRICT_MODE:
|
||||
throw $e;
|
||||
case self::LAX_MODE:
|
||||
if ($this->isErrorImportant($annotationClass, $refClass->getDocComment(), $refClass->getName())) {
|
||||
throw $e;
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected mode '{$this->mode}'."); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
$refClass = $refClass->getParentClass();
|
||||
} while ($refClass);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a method annotation and handles correctly errors.
|
||||
*
|
||||
* @return null|object
|
||||
*/
|
||||
private function getMethodAnnotation(ReflectionMethod $refMethod, string $annotationClass)
|
||||
{
|
||||
$cacheKey = $refMethod->getDeclaringClass()->getName() . '::' . $refMethod->getName() . '_' . $annotationClass;
|
||||
if (isset($this->methodAnnotationCache[$cacheKey])) {
|
||||
return $this->methodAnnotationCache[$cacheKey];
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->methodAnnotationCache[$cacheKey] = $this->reader->getMethodAnnotation($refMethod, $annotationClass);
|
||||
} catch (AnnotationException $e) {
|
||||
switch ($this->mode) {
|
||||
case self::STRICT_MODE:
|
||||
throw $e;
|
||||
case self::LAX_MODE:
|
||||
if ($this->isErrorImportant($annotationClass, $refMethod->getDocComment(), $refMethod->getDeclaringClass()->getName())) {
|
||||
throw $e;
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected mode '{$this->mode}'."); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the annotation class name is part of the docblock comment.
|
||||
*/
|
||||
private function isErrorImportant(string $annotationClass, string $docComment, string $className): bool
|
||||
{
|
||||
foreach ($this->strictNamespaces as $strictNamespace) {
|
||||
if (strpos($className, $strictNamespace) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$shortAnnotationClass = substr($annotationClass, strrpos($annotationClass, '\\') + 1);
|
||||
return strpos($docComment, '@' . $shortAnnotationClass) !== false;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Hyperf\Di\MetadataCollector;
|
||||
|
||||
class ClassCollector extends MetadataCollector
|
||||
{
|
||||
protected static array $container = [];
|
||||
|
||||
public static function collect(string $class)
|
||||
{
|
||||
if (! in_array($class, self::$container)) {
|
||||
self::$container[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getClasses()
|
||||
{
|
||||
return self::$container;
|
||||
}
|
||||
}
|
@ -12,18 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use GraphQL\Type\Schema;
|
||||
use TheCodingMachine\GraphQLite\Hydrators\FactoryHydrator;
|
||||
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategy;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\QueryProviderInterface;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\Security\FailAuthenticationService;
|
||||
use TheCodingMachine\GraphQLite\Security\FailAuthorizationService;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
@ -31,23 +20,13 @@ class ConfigProvider
|
||||
{
|
||||
return [
|
||||
'dependencies' => [
|
||||
Schema::class => \TheCodingMachine\GraphQLite\Schema::class,
|
||||
QueryProviderInterface::class => QueryProvider::class,
|
||||
RecursiveTypeMapperInterface::class => RecursiveTypeMapperFactory::class,
|
||||
Reader::class => ReaderFactory::class,
|
||||
HydratorInterface::class => FactoryHydrator::class,
|
||||
AuthenticationServiceInterface::class => FailAuthenticationService::class,
|
||||
AuthorizationServiceInterface::class => FailAuthorizationService::class,
|
||||
NamingStrategyInterface::class => NamingStrategy::class,
|
||||
Schema::class => SchemaFactory::class,
|
||||
],
|
||||
'annotations' => [
|
||||
'scan' => [
|
||||
'paths' => [
|
||||
__DIR__,
|
||||
],
|
||||
'collectors' => [
|
||||
ClassCollector::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -1,799 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\NonNull;
|
||||
use GraphQL\Type\Definition\Type as GraphQLType;
|
||||
use Hyperf\GraphQL\Annotation\Field;
|
||||
use Hyperf\GraphQL\Annotation\Logged;
|
||||
use Hyperf\GraphQL\Annotation\Mutation;
|
||||
use Hyperf\GraphQL\Annotation\Query;
|
||||
use Iterator;
|
||||
use IteratorAggregate;
|
||||
use phpDocumentor\Reflection\DocBlock;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
|
||||
use phpDocumentor\Reflection\Fqsen;
|
||||
use phpDocumentor\Reflection\Type;
|
||||
use phpDocumentor\Reflection\Types\Array_;
|
||||
use phpDocumentor\Reflection\Types\Boolean;
|
||||
use phpDocumentor\Reflection\Types\Compound;
|
||||
use phpDocumentor\Reflection\Types\Float_;
|
||||
use phpDocumentor\Reflection\Types\Integer;
|
||||
use phpDocumentor\Reflection\Types\Iterable_;
|
||||
use phpDocumentor\Reflection\Types\Mixed_;
|
||||
use phpDocumentor\Reflection\Types\Null_;
|
||||
use phpDocumentor\Reflection\Types\Nullable;
|
||||
use phpDocumentor\Reflection\Types\Object_;
|
||||
use phpDocumentor\Reflection\Types\Self_;
|
||||
use phpDocumentor\Reflection\Types\String_;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
use TheCodingMachine\GraphQLite\Annotations\SourceField;
|
||||
use TheCodingMachine\GraphQLite\FieldNotFoundException;
|
||||
use TheCodingMachine\GraphQLite\FromSourceFieldsInterface;
|
||||
use TheCodingMachine\GraphQLite\GraphQLException;
|
||||
use TheCodingMachine\GraphQLite\InvalidDocBlockException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\MissingAnnotationException;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\QueryField;
|
||||
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\TypeMappingException;
|
||||
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
|
||||
use TheCodingMachine\GraphQLite\Types\CustomTypesRegistry;
|
||||
use TheCodingMachine\GraphQLite\Types\DateTimeType;
|
||||
use TheCodingMachine\GraphQLite\Types\ID;
|
||||
use TheCodingMachine\GraphQLite\Types\TypeResolver;
|
||||
use TheCodingMachine\GraphQLite\Types\UnionType;
|
||||
|
||||
use function array_merge;
|
||||
use function get_parent_class;
|
||||
use function iterator_to_array;
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* A class in charge if returning list of fields for queries / mutations / entities / input types.
|
||||
*/
|
||||
class FieldsBuilder
|
||||
{
|
||||
/**
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $annotationReader;
|
||||
|
||||
/**
|
||||
* @var RecursiveTypeMapperInterface
|
||||
*/
|
||||
private $typeMapper;
|
||||
|
||||
/**
|
||||
* @var ArgumentResolver
|
||||
*/
|
||||
private $argumentResolver;
|
||||
|
||||
/**
|
||||
* @var AuthenticationServiceInterface
|
||||
*/
|
||||
private $authenticationService;
|
||||
|
||||
/**
|
||||
* @var AuthorizationServiceInterface
|
||||
*/
|
||||
private $authorizationService;
|
||||
|
||||
/**
|
||||
* @var CachedDocBlockFactory
|
||||
*/
|
||||
private $cachedDocBlockFactory;
|
||||
|
||||
/**
|
||||
* @var TypeResolver
|
||||
*/
|
||||
private $typeResolver;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
public function __construct(
|
||||
AnnotationReader $annotationReader,
|
||||
RecursiveTypeMapperInterface $typeMapper,
|
||||
ArgumentResolver $argumentResolver,
|
||||
AuthenticationServiceInterface $authenticationService,
|
||||
AuthorizationServiceInterface $authorizationService,
|
||||
TypeResolver $typeResolver,
|
||||
CachedDocBlockFactory $cachedDocBlockFactory,
|
||||
NamingStrategyInterface $namingStrategy
|
||||
) {
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->typeMapper = $typeMapper;
|
||||
$this->argumentResolver = $argumentResolver;
|
||||
$this->authenticationService = $authenticationService;
|
||||
$this->authorizationService = $authorizationService;
|
||||
$this->typeResolver = $typeResolver;
|
||||
$this->cachedDocBlockFactory = $cachedDocBlockFactory;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
}
|
||||
|
||||
// TODO: Add RecursiveTypeMapper in the list of parameters for getQueries and REMOVE the ControllerQueryProviderFactory.
|
||||
|
||||
/**
|
||||
* @param object $controller
|
||||
* @return QueryField[]
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getQueries($controller): array
|
||||
{
|
||||
return $this->getFieldsByAnnotations($controller, Query::class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $controller
|
||||
* @return QueryField[]
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getMutations($controller): array
|
||||
{
|
||||
return $this->getFieldsByAnnotations($controller, Mutation::class, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $controller
|
||||
* @return array<string, QueryField> queryField indexed by name
|
||||
*/
|
||||
public function getFields($controller): array
|
||||
{
|
||||
$fieldAnnotations = $this->getFieldsByAnnotations($controller, Field::class, true);
|
||||
|
||||
$refClass = new ReflectionClass($controller);
|
||||
|
||||
/** @var SourceField[] $sourceFields */
|
||||
$sourceFields = $this->annotationReader->getSourceFields($refClass);
|
||||
|
||||
if ($controller instanceof FromSourceFieldsInterface) {
|
||||
$sourceFields = array_merge($sourceFields, $controller->getSourceFields());
|
||||
}
|
||||
|
||||
$fieldsFromSourceFields = $this->getQueryFieldsFromSourceFields($sourceFields, $refClass);
|
||||
|
||||
$fields = [];
|
||||
foreach ($fieldAnnotations as $field) {
|
||||
$fields[$field->name] = $field;
|
||||
}
|
||||
foreach ($fieldsFromSourceFields as $field) {
|
||||
$fields[$field->name] = $field;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track Field annotation in a self targeted type.
|
||||
*
|
||||
* @return array<string, QueryField> queryField indexed by name
|
||||
*/
|
||||
public function getSelfFields(string $className): array
|
||||
{
|
||||
$fieldAnnotations = $this->getFieldsByAnnotations(null, Field::class, false, $className);
|
||||
|
||||
$refClass = new ReflectionClass($className);
|
||||
|
||||
/** @var SourceField[] $sourceFields */
|
||||
$sourceFields = $this->annotationReader->getSourceFields($refClass);
|
||||
|
||||
$fieldsFromSourceFields = $this->getQueryFieldsFromSourceFields($sourceFields, $refClass);
|
||||
|
||||
$fields = [];
|
||||
foreach ($fieldAnnotations as $field) {
|
||||
$fields[$field->name] = $field;
|
||||
}
|
||||
foreach ($fieldsFromSourceFields as $field) {
|
||||
$fields[$field->name] = $field;
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $refMethod a method annotated with a Factory annotation
|
||||
* @return array<string, array<int, mixed>> returns an array of fields as accepted by the InputObjectType constructor
|
||||
*/
|
||||
public function getInputFields(ReflectionMethod $refMethod): array
|
||||
{
|
||||
$docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod);
|
||||
// $docBlockComment = $docBlockObj->getSummary()."\n".$docBlockObj->getDescription()->render();
|
||||
|
||||
$parameters = $refMethod->getParameters();
|
||||
|
||||
return $this->mapParameters($parameters, $docBlockObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $controller
|
||||
* @param bool $injectSource whether to inject the source object or not as the first argument
|
||||
* @return QueryField[]
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function getFieldsByAnnotations($controller, string $annotationName, bool $injectSource, ?string $sourceClassName = null): array
|
||||
{
|
||||
if ($sourceClassName !== null) {
|
||||
$refClass = new ReflectionClass($sourceClassName);
|
||||
} else {
|
||||
$refClass = new ReflectionClass($controller);
|
||||
}
|
||||
|
||||
$queryList = [];
|
||||
|
||||
$oldDeclaringClass = null;
|
||||
$context = null;
|
||||
|
||||
$closestMatchingTypeClass = null;
|
||||
if ($annotationName === Field::class) {
|
||||
$parent = get_parent_class($refClass->getName());
|
||||
if ($parent !== null) {
|
||||
$closestMatchingTypeClass = $this->typeMapper->findClosestMatchingParent((string) $parent);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($refClass->getMethods() 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.
|
||||
break;
|
||||
}
|
||||
|
||||
// First, let's check the "Query" or "Mutation" or "Field" annotation
|
||||
$queryAnnotation = $this->annotationReader->getRequestAnnotation($refMethod, $annotationName);
|
||||
|
||||
if ($queryAnnotation !== null) {
|
||||
$unauthorized = false;
|
||||
if (! $this->isAuthorized($refMethod)) {
|
||||
$failWith = $this->annotationReader->getFailWithAnnotation($refMethod);
|
||||
if ($failWith === null) {
|
||||
continue;
|
||||
}
|
||||
$unauthorized = true;
|
||||
}
|
||||
|
||||
$docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod);
|
||||
$docBlockComment = $docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render();
|
||||
|
||||
$methodName = $refMethod->getName();
|
||||
$name = $queryAnnotation->getName() ?: $this->namingStrategy->getFieldNameFromMethodName($methodName);
|
||||
|
||||
$parameters = $refMethod->getParameters();
|
||||
if ($injectSource === true) {
|
||||
$first_parameter = array_shift($parameters);
|
||||
// TODO: check that $first_parameter type is correct.
|
||||
}
|
||||
|
||||
$args = $this->mapParameters($parameters, $docBlockObj);
|
||||
|
||||
if ($queryAnnotation->getOutputType()) {
|
||||
try {
|
||||
$type = $this->typeResolver->mapNameToOutputType($queryAnnotation->getOutputType());
|
||||
} catch (CannotMapTypeExceptionInterface $e) {
|
||||
throw CannotMapTypeException::wrapWithReturnInfo($e, $refMethod);
|
||||
}
|
||||
} else {
|
||||
$type = $this->mapReturnType($refMethod, $docBlockObj);
|
||||
}
|
||||
|
||||
if ($unauthorized) {
|
||||
$failWithValue = $failWith->getValue();
|
||||
$callable = function () use ($failWithValue) {
|
||||
return $failWithValue;
|
||||
};
|
||||
if ($failWithValue === null && $type instanceof NonNull) {
|
||||
$type = $type->getWrappedType();
|
||||
}
|
||||
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
|
||||
} else {
|
||||
$callable = [$controller, $methodName];
|
||||
if ($sourceClassName !== null) {
|
||||
$queryList[] = new QueryField($name, $type, $args, null, $callable[1], $this->argumentResolver, $docBlockComment, $injectSource);
|
||||
} else {
|
||||
$queryList[] = new QueryField($name, $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, $injectSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $queryList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GraphQLType|OutputType
|
||||
*/
|
||||
private function mapReturnType(ReflectionMethod $refMethod, DocBlock $docBlockObj): GraphQLType
|
||||
{
|
||||
$returnType = $refMethod->getReturnType();
|
||||
if ($returnType !== null) {
|
||||
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
|
||||
$phpdocType = $typeResolver->resolve((string) $returnType);
|
||||
$phpdocType = $this->resolveSelf($phpdocType, $refMethod->getDeclaringClass());
|
||||
} else {
|
||||
$phpdocType = new Mixed_();
|
||||
}
|
||||
|
||||
$docBlockReturnType = $this->getDocBlocReturnType($docBlockObj, $refMethod);
|
||||
|
||||
try {
|
||||
/** @var GraphQLType|OutputType $type */
|
||||
$type = $this->mapType($phpdocType, $docBlockReturnType, $returnType ? $returnType->allowsNull() : false, false);
|
||||
} catch (TypeMappingException $e) {
|
||||
throw TypeMappingException::wrapWithReturnInfo($e, $refMethod);
|
||||
} catch (CannotMapTypeExceptionInterface $e) {
|
||||
throw CannotMapTypeException::wrapWithReturnInfo($e, $refMethod);
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function getDocBlocReturnType(DocBlock $docBlock, ReflectionMethod $refMethod): ?Type
|
||||
{
|
||||
/** @var Return_[] $returnTypeTags */
|
||||
$returnTypeTags = $docBlock->getTagsByName('return');
|
||||
if (count($returnTypeTags) > 1) {
|
||||
throw InvalidDocBlockException::tooManyReturnTags($refMethod);
|
||||
}
|
||||
$docBlockReturnType = null;
|
||||
if (isset($returnTypeTags[0])) {
|
||||
$docBlockReturnType = $returnTypeTags[0]->getType();
|
||||
}
|
||||
return $docBlockReturnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, SourceFieldInterface> $sourceFields
|
||||
* @return QueryField[]
|
||||
* @throws CannotMapTypeException
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function getQueryFieldsFromSourceFields(array $sourceFields, ReflectionClass $refClass): array
|
||||
{
|
||||
if (empty($sourceFields)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$typeField = $this->annotationReader->getTypeAnnotation($refClass);
|
||||
$extendTypeField = $this->annotationReader->getExtendTypeAnnotation($refClass);
|
||||
|
||||
if ($typeField !== null) {
|
||||
$objectClass = $typeField->getClass();
|
||||
} elseif ($extendTypeField !== null) {
|
||||
$objectClass = $extendTypeField->getClass();
|
||||
} else {
|
||||
throw MissingAnnotationException::missingTypeExceptionToUseSourceField();
|
||||
}
|
||||
|
||||
$objectRefClass = new ReflectionClass($objectClass);
|
||||
|
||||
$oldDeclaringClass = null;
|
||||
$context = null;
|
||||
$queryList = [];
|
||||
|
||||
foreach ($sourceFields as $sourceField) {
|
||||
// Ignore the field if we must be logged.
|
||||
$right = $sourceField->getRight();
|
||||
$unauthorized = false;
|
||||
if (($sourceField->isLogged() && ! $this->authenticationService->isLogged())
|
||||
|| ($right !== null && ! $this->authorizationService->isAllowed($right->getName()))) {
|
||||
if (! $sourceField->canFailWith()) {
|
||||
continue;
|
||||
}
|
||||
$unauthorized = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$refMethod = $this->getMethodFromPropertyName($objectRefClass, $sourceField->getName());
|
||||
} catch (FieldNotFoundException $e) {
|
||||
throw FieldNotFoundException::wrapWithCallerInfo($e, $refClass->getName());
|
||||
}
|
||||
|
||||
$methodName = $refMethod->getName();
|
||||
|
||||
$docBlockObj = $this->cachedDocBlockFactory->getDocBlock($refMethod);
|
||||
$docBlockComment = $docBlockObj->getSummary() . "\n" . $docBlockObj->getDescription()->render();
|
||||
|
||||
$args = $this->mapParameters($refMethod->getParameters(), $docBlockObj);
|
||||
|
||||
if ($sourceField->isId()) {
|
||||
$type = GraphQLType::id();
|
||||
if (! $refMethod->getReturnType()->allowsNull()) {
|
||||
$type = GraphQLType::nonNull($type);
|
||||
}
|
||||
} elseif ($sourceField->getOutputType()) {
|
||||
try {
|
||||
$type = $this->typeResolver->mapNameToOutputType($sourceField->getOutputType());
|
||||
} catch (CannotMapTypeExceptionInterface $e) {
|
||||
throw CannotMapTypeException::wrapWithSourceField($e, $refClass, $sourceField);
|
||||
}
|
||||
} else {
|
||||
$type = $this->mapReturnType($refMethod, $docBlockObj);
|
||||
}
|
||||
|
||||
if (! $unauthorized) {
|
||||
$queryList[] = new QueryField($sourceField->getName(), $type, $args, null, $methodName, $this->argumentResolver, $docBlockComment, false);
|
||||
} else {
|
||||
$failWithValue = $sourceField->getFailWith();
|
||||
$callable = function () use ($failWithValue) {
|
||||
return $failWithValue;
|
||||
};
|
||||
if ($failWithValue === null && $type instanceof NonNull) {
|
||||
$type = $type->getWrappedType();
|
||||
}
|
||||
$queryList[] = new QueryField($sourceField->getName(), $type, $args, $callable, null, $this->argumentResolver, $docBlockComment, false);
|
||||
}
|
||||
}
|
||||
return $queryList;
|
||||
}
|
||||
|
||||
private function getMethodFromPropertyName(ReflectionClass $reflectionClass, string $propertyName): ReflectionMethod
|
||||
{
|
||||
if ($reflectionClass->hasMethod($propertyName)) {
|
||||
$methodName = $propertyName;
|
||||
} else {
|
||||
$upperCasePropertyName = ucfirst($propertyName);
|
||||
if ($reflectionClass->hasMethod('get' . $upperCasePropertyName)) {
|
||||
$methodName = 'get' . $upperCasePropertyName;
|
||||
} elseif ($reflectionClass->hasMethod('is' . $upperCasePropertyName)) {
|
||||
$methodName = 'is' . $upperCasePropertyName;
|
||||
} else {
|
||||
throw FieldNotFoundException::missingField($reflectionClass->getName(), $propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
return $reflectionClass->getMethod($methodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the Logged and Right annotations.
|
||||
*/
|
||||
private function isAuthorized(ReflectionMethod $reflectionMethod): bool
|
||||
{
|
||||
$loggedAnnotation = $this->annotationReader->getLoggedAnnotation($reflectionMethod);
|
||||
|
||||
if ($loggedAnnotation !== null && ! $this->authenticationService->isLogged()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rightAnnotation = $this->annotationReader->getRightAnnotation($reflectionMethod);
|
||||
|
||||
if ($rightAnnotation !== null && ! $this->authorizationService->isAllowed($rightAnnotation->getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: there is a bug in $refMethod->allowsNull that forces us to use $standardRefMethod->allowsNull instead.
|
||||
*
|
||||
* @param ReflectionParameter[] $refParameters
|
||||
* @return array[] An array of ['type'=>Type, 'defaultValue'=>val]
|
||||
* @throws MissingTypeHintException
|
||||
*/
|
||||
private function mapParameters(array $refParameters, DocBlock $docBlock): array
|
||||
{
|
||||
$args = [];
|
||||
|
||||
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
|
||||
|
||||
foreach ($refParameters as $parameter) {
|
||||
$parameterType = $parameter->getType();
|
||||
$allowsNull = $parameterType === null ? true : $parameterType->allowsNull();
|
||||
|
||||
$type = (string) $parameterType;
|
||||
if ($type === '') {
|
||||
throw MissingTypeHintException::missingTypeHint($parameter);
|
||||
}
|
||||
$phpdocType = $typeResolver->resolve($type);
|
||||
$phpdocType = $this->resolveSelf($phpdocType, $parameter->getDeclaringClass());
|
||||
|
||||
/** @var DocBlock\Tags\Param[] $paramTags */
|
||||
$paramTags = $docBlock->getTagsByName('param');
|
||||
$docBlockType = null;
|
||||
foreach ($paramTags as $paramTag) {
|
||||
if ($paramTag->getVariableName() === $parameter->getName()) {
|
||||
$docBlockType = $paramTag->getType();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$arr = [
|
||||
'type' => $this->mapType($phpdocType, $docBlockType, $allowsNull || $parameter->isDefaultValueAvailable(), true),
|
||||
];
|
||||
} catch (TypeMappingException $e) {
|
||||
throw TypeMappingException::wrapWithParamInfo($e, $parameter);
|
||||
} catch (CannotMapTypeExceptionInterface $e) {
|
||||
throw CannotMapTypeException::wrapWithParamInfo($e, $parameter);
|
||||
}
|
||||
|
||||
if ($parameter->allowsNull()) {
|
||||
$arr['defaultValue'] = null;
|
||||
}
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
$arr['defaultValue'] = $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
$args[$parameter->getName()] = $arr;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
private function mapType(Type $type, ?Type $docBlockType, bool $isNullable, bool $mapToInputType): GraphQLType
|
||||
{
|
||||
$graphQlType = null;
|
||||
|
||||
if ($type instanceof Array_ || $type instanceof Iterable_ || $type instanceof Mixed_) {
|
||||
$graphQlType = $this->mapDocBlockType($type, $docBlockType, $isNullable, $mapToInputType);
|
||||
} else {
|
||||
try {
|
||||
$graphQlType = $this->toGraphQlType($type, null, $mapToInputType);
|
||||
if (! $isNullable) {
|
||||
$graphQlType = GraphQLType::nonNull($graphQlType);
|
||||
}
|
||||
} catch (CannotMapTypeExceptionInterface|TypeMappingException $e) {
|
||||
// Is the type iterable? If yes, let's analyze the docblock
|
||||
// TODO: it would be better not to go through an exception for this.
|
||||
if ($type instanceof Object_) {
|
||||
$fqcn = (string) $type->getFqsen();
|
||||
$refClass = new ReflectionClass($fqcn);
|
||||
// Note : $refClass->isIterable() is only accessible in PHP 7.2
|
||||
if ($refClass->implementsInterface(Iterator::class) || $refClass->implementsInterface(IteratorAggregate::class)) {
|
||||
$graphQlType = $this->mapIteratorDocBlockType($type, $docBlockType, $isNullable);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $graphQlType;
|
||||
}
|
||||
|
||||
private function mapDocBlockType(Type $type, ?Type $docBlockType, bool $isNullable, bool $mapToInputType): GraphQLType
|
||||
{
|
||||
if ($docBlockType === null) {
|
||||
throw TypeMappingException::createFromType($type);
|
||||
}
|
||||
if (! $isNullable) {
|
||||
// Let's check a "null" value in the docblock
|
||||
$isNullable = $this->isNullable($docBlockType);
|
||||
}
|
||||
|
||||
$filteredDocBlockTypes = $this->typesWithoutNullable($docBlockType);
|
||||
if (empty($filteredDocBlockTypes)) {
|
||||
throw TypeMappingException::createFromType($type);
|
||||
}
|
||||
|
||||
$unionTypes = [];
|
||||
$lastException = null;
|
||||
foreach ($filteredDocBlockTypes as $singleDocBlockType) {
|
||||
try {
|
||||
$unionTypes[] = $this->toGraphQlType($this->dropNullableType($singleDocBlockType), null, $mapToInputType);
|
||||
} catch (CannotMapTypeExceptionInterface|TypeMappingException $e) {
|
||||
// We have several types. It is ok not to be able to match one.
|
||||
$lastException = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($unionTypes) && $lastException !== null) {
|
||||
throw $lastException;
|
||||
}
|
||||
|
||||
if (count($unionTypes) === 1) {
|
||||
$graphQlType = $unionTypes[0];
|
||||
} else {
|
||||
$graphQlType = new UnionType($unionTypes, $this->typeMapper);
|
||||
}
|
||||
|
||||
/* elseif (count($filteredDocBlockTypes) === 1) {
|
||||
$graphQlType = $this->toGraphQlType($filteredDocBlockTypes[0], $mapToInputType);
|
||||
} else {
|
||||
throw new GraphQLException('Union types are not supported (yet)');
|
||||
//$graphQlTypes = array_map([$this, 'toGraphQlType'], $filteredDocBlockTypes);
|
||||
//$$graphQlType = new UnionType($graphQlTypes);
|
||||
}*/
|
||||
|
||||
if (! $isNullable) {
|
||||
$graphQlType = GraphQLType::nonNull($graphQlType);
|
||||
}
|
||||
return $graphQlType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a type where the main PHP type is an iterator.
|
||||
*/
|
||||
private function mapIteratorDocBlockType(Type $type, ?Type $docBlockType, bool $isNullable): GraphQLType
|
||||
{
|
||||
if ($docBlockType === null) {
|
||||
throw TypeMappingException::createFromType($type);
|
||||
}
|
||||
if (! $isNullable) {
|
||||
// Let's check a "null" value in the docblock
|
||||
$isNullable = $this->isNullable($docBlockType);
|
||||
}
|
||||
|
||||
$filteredDocBlockTypes = $this->typesWithoutNullable($docBlockType);
|
||||
if (empty($filteredDocBlockTypes)) {
|
||||
throw TypeMappingException::createFromType($type);
|
||||
}
|
||||
|
||||
$unionTypes = [];
|
||||
$lastException = null;
|
||||
foreach ($filteredDocBlockTypes as $singleDocBlockType) {
|
||||
try {
|
||||
$singleDocBlockType = $this->getTypeInArray($singleDocBlockType);
|
||||
if ($singleDocBlockType !== null) {
|
||||
$subGraphQlType = $this->toGraphQlType($singleDocBlockType, null, false);
|
||||
} else {
|
||||
$subGraphQlType = null;
|
||||
}
|
||||
|
||||
$unionTypes[] = $this->toGraphQlType($type, $subGraphQlType, false);
|
||||
|
||||
// TODO: add here a scan of the $type variable and do stuff if it is iterable.
|
||||
// TODO: remove the iterator type if specified in the docblock (@return Iterator|User[])
|
||||
// TODO: check there is at least one array (User[])
|
||||
} catch (CannotMapTypeExceptionInterface|TypeMappingException $e) {
|
||||
// We have several types. It is ok not to be able to match one.
|
||||
$lastException = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($unionTypes) && $lastException !== null) {
|
||||
// We have an issue, let's try without the subType
|
||||
return $this->mapDocBlockType($type, $docBlockType, $isNullable, false);
|
||||
}
|
||||
|
||||
if (count($unionTypes) === 1) {
|
||||
$graphQlType = $unionTypes[0];
|
||||
} else {
|
||||
$graphQlType = new UnionType($unionTypes, $this->typeMapper);
|
||||
}
|
||||
|
||||
if (! $isNullable) {
|
||||
$graphQlType = GraphQLType::nonNull($graphQlType);
|
||||
}
|
||||
return $graphQlType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a Type to a GraphQL type.
|
||||
* Does not deal with nullable.
|
||||
*
|
||||
* @return GraphQLType (GraphQLType&InputType)|(GraphQLType&OutputType)
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
*/
|
||||
private function toGraphQlType(Type $type, ?GraphQLType $subType, bool $mapToInputType): GraphQLType
|
||||
{
|
||||
if ($type instanceof Integer) {
|
||||
return GraphQLType::int();
|
||||
}
|
||||
if ($type instanceof String_) {
|
||||
return GraphQLType::string();
|
||||
}
|
||||
if ($type instanceof Boolean) {
|
||||
return GraphQLType::boolean();
|
||||
}
|
||||
if ($type instanceof Float_) {
|
||||
return GraphQLType::float();
|
||||
}
|
||||
if ($type instanceof Object_) {
|
||||
$fqcn = (string) $type->getFqsen();
|
||||
switch ($fqcn) {
|
||||
case '\DateTimeImmutable':
|
||||
case '\DateTimeInterface':
|
||||
return DateTimeType::getInstance();
|
||||
case '\\' . UploadedFileInterface::class:
|
||||
return CustomTypesRegistry::getUploadType();
|
||||
case '\DateTime':
|
||||
throw new GraphQLException('Type-hinting a parameter against DateTime is not allowed. Please use the DateTimeImmutable type instead.');
|
||||
case '\\' . ID::class:
|
||||
return GraphQLType::id();
|
||||
default:
|
||||
$className = ltrim((string) $type->getFqsen(), '\\');
|
||||
if ($mapToInputType) {
|
||||
return $this->typeMapper->mapClassToInputType($className);
|
||||
}
|
||||
return $this->typeMapper->mapClassToInterfaceOrType($className, $subType);
|
||||
}
|
||||
} elseif ($type instanceof Array_) {
|
||||
return GraphQLType::listOf(GraphQLType::nonNull($this->toGraphQlType($type->getValueType(), $subType, $mapToInputType)));
|
||||
} else {
|
||||
throw TypeMappingException::createFromType($type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes "null" from the type (if it is compound). Return an array of types (not a Compound type).
|
||||
*/
|
||||
private function typesWithoutNullable(Type $docBlockTypeHint): array
|
||||
{
|
||||
if ($docBlockTypeHint instanceof Compound) {
|
||||
$docBlockTypeHints = iterator_to_array($docBlockTypeHint);
|
||||
} else {
|
||||
$docBlockTypeHints = [$docBlockTypeHint];
|
||||
}
|
||||
return array_filter($docBlockTypeHints, function ($item) {
|
||||
return ! $item instanceof Null_;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops "Nullable" types and return the core type.
|
||||
*/
|
||||
private function dropNullableType(Type $typeHint): Type
|
||||
{
|
||||
if ($typeHint instanceof Nullable) {
|
||||
return $typeHint->getActualType();
|
||||
}
|
||||
return $typeHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a list type.
|
||||
*/
|
||||
private function getTypeInArray(Type $typeHint): ?Type
|
||||
{
|
||||
$typeHint = $this->dropNullableType($typeHint);
|
||||
|
||||
if (! $typeHint instanceof Array_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->dropNullableType($typeHint->getValueType());
|
||||
}
|
||||
|
||||
private function isNullable(Type $docBlockTypeHint): bool
|
||||
{
|
||||
if ($docBlockTypeHint instanceof Null_) {
|
||||
return true;
|
||||
}
|
||||
if ($docBlockTypeHint instanceof Compound) {
|
||||
foreach ($docBlockTypeHint as $type) {
|
||||
if ($type instanceof Null_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves "self" types into the class type.
|
||||
*/
|
||||
private function resolveSelf(Type $type, ReflectionClass $reflectionClass): Type
|
||||
{
|
||||
if ($type instanceof Self_) {
|
||||
return new Object_(new Fqsen('\\' . $reflectionClass->getName()));
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use TheCodingMachine\GraphQLite\Hydrators\HydratorInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\Reflection\CachedDocBlockFactory;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
|
||||
use TheCodingMachine\GraphQLite\Types\TypeResolver;
|
||||
|
||||
class FieldsBuilderFactory
|
||||
{
|
||||
/**
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $annotationReader;
|
||||
|
||||
/**
|
||||
* @var HydratorInterface
|
||||
*/
|
||||
private $hydrator;
|
||||
|
||||
/**
|
||||
* @var AuthenticationServiceInterface
|
||||
*/
|
||||
private $authenticationService;
|
||||
|
||||
/**
|
||||
* @var AuthorizationServiceInterface
|
||||
*/
|
||||
private $authorizationService;
|
||||
|
||||
/**
|
||||
* @var CachedDocBlockFactory
|
||||
*/
|
||||
private $cachedDocBlockFactory;
|
||||
|
||||
/**
|
||||
* @var TypeResolver
|
||||
*/
|
||||
private $typeResolver;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
public function __construct(
|
||||
AnnotationReader $annotationReader,
|
||||
HydratorInterface $hydrator,
|
||||
AuthenticationServiceInterface $authenticationService,
|
||||
AuthorizationServiceInterface $authorizationService,
|
||||
TypeResolver $typeResolver,
|
||||
CachedDocBlockFactory $cachedDocBlockFactory,
|
||||
NamingStrategyInterface $namingStrategy
|
||||
) {
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->hydrator = $hydrator;
|
||||
$this->authenticationService = $authenticationService;
|
||||
$this->authorizationService = $authorizationService;
|
||||
$this->typeResolver = $typeResolver;
|
||||
$this->cachedDocBlockFactory = $cachedDocBlockFactory;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
}
|
||||
|
||||
public function buildFieldsBuilder(RecursiveTypeMapperInterface $typeMapper): FieldsBuilder
|
||||
{
|
||||
return new FieldsBuilder(
|
||||
$this->annotationReader,
|
||||
$typeMapper,
|
||||
new ArgumentResolver($this->hydrator),
|
||||
$this->authenticationService,
|
||||
$this->authorizationService,
|
||||
$this->typeResolver,
|
||||
$this->cachedDocBlockFactory,
|
||||
$this->namingStrategy
|
||||
);
|
||||
}
|
||||
}
|
@ -24,14 +24,8 @@ use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class GraphQLMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var Schema
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
public function __construct(Schema $schema)
|
||||
public function __construct(protected Schema $schema)
|
||||
{
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
@ -42,7 +36,7 @@ class GraphQLMiddleware implements MiddlewareInterface
|
||||
|
||||
$input = $request->getParsedBody();
|
||||
$query = $input['query'];
|
||||
$variableValues = isset($input['variables']) ? $input['variables'] : null;
|
||||
$variableValues = $input['variables'] ?? null;
|
||||
|
||||
$result = GraphQL::executeQuery($this->schema, $query, null, null, $variableValues);
|
||||
return ResponseContext::get()->setBody(new SwooleStream(Json::encode($result)));
|
||||
|
@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionMethod;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
|
||||
|
||||
/**
|
||||
* This class is in charge of creating Webonyx InputTypes from Factory annotations.
|
||||
*/
|
||||
class InputTypeGenerator
|
||||
{
|
||||
/**
|
||||
* @var FieldsBuilderFactory
|
||||
*/
|
||||
private $fieldsBuilderFactory;
|
||||
|
||||
/**
|
||||
* @var array<string, InputObjectType>
|
||||
*/
|
||||
private $cache = [];
|
||||
|
||||
/**
|
||||
* @var ArgumentResolver
|
||||
*/
|
||||
private $argumentResolver;
|
||||
|
||||
/**
|
||||
* @var InputTypeUtils
|
||||
*/
|
||||
private $inputTypeUtils;
|
||||
|
||||
public function __construct(
|
||||
InputTypeUtils $inputTypeUtils,
|
||||
FieldsBuilderFactory $fieldsBuilderFactory,
|
||||
ArgumentResolver $argumentResolver
|
||||
) {
|
||||
$this->inputTypeUtils = $inputTypeUtils;
|
||||
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
|
||||
$this->argumentResolver = $argumentResolver;
|
||||
}
|
||||
|
||||
public function mapFactoryMethod(string $factory, string $methodName, RecursiveTypeMapperInterface $recursiveTypeMapper, ContainerInterface $container): InputObjectType
|
||||
{
|
||||
$method = new ReflectionMethod($factory, $methodName);
|
||||
|
||||
if ($method->isStatic()) {
|
||||
$object = $factory;
|
||||
} else {
|
||||
$object = $container->get($factory);
|
||||
}
|
||||
|
||||
[$inputName, $className] = $this->inputTypeUtils->getInputTypeNameAndClassName($method);
|
||||
|
||||
if (! isset($this->cache[$inputName])) {
|
||||
// TODO: add comment argument.
|
||||
$this->cache[$inputName] = new ResolvableInputObjectType($inputName, $this->fieldsBuilderFactory, $recursiveTypeMapper, $object, $methodName, $this->argumentResolver, null);
|
||||
}
|
||||
|
||||
return $this->cache[$inputName];
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use phpDocumentor\Reflection\Fqsen;
|
||||
use phpDocumentor\Reflection\Type;
|
||||
use phpDocumentor\Reflection\TypeResolver;
|
||||
use phpDocumentor\Reflection\Types\Object_;
|
||||
use phpDocumentor\Reflection\Types\Self_;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use RuntimeException;
|
||||
use TheCodingMachine\GraphQLite\MissingTypeHintException;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
|
||||
class InputTypeUtils
|
||||
{
|
||||
/**
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $annotationReader;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
public function __construct(
|
||||
AnnotationReader $annotationReader,
|
||||
NamingStrategyInterface $namingStrategy
|
||||
) {
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with 2 elements: [ $inputName, $className ].
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getInputTypeNameAndClassName(ReflectionMethod $method): array
|
||||
{
|
||||
$fqsen = ltrim((string) $this->validateReturnType($method), '\\');
|
||||
$factory = $this->annotationReader->getFactoryAnnotation($method);
|
||||
if ($factory === null) {
|
||||
throw new RuntimeException($method->getDeclaringClass()->getName() . '::' . $method->getName() . ' has no @Factory annotation.');
|
||||
}
|
||||
return [$this->namingStrategy->getInputTypeName($fqsen, $factory), $fqsen];
|
||||
}
|
||||
|
||||
private function validateReturnType(ReflectionMethod $refMethod): Fqsen
|
||||
{
|
||||
$returnType = $refMethod->getReturnType();
|
||||
if ($returnType === null) {
|
||||
throw MissingTypeHintException::missingReturnType($refMethod);
|
||||
}
|
||||
|
||||
if ($returnType->allowsNull()) {
|
||||
throw MissingTypeHintException::nullableReturnType($refMethod);
|
||||
}
|
||||
|
||||
$type = (string) $returnType;
|
||||
|
||||
$typeResolver = new TypeResolver();
|
||||
|
||||
$phpdocType = $typeResolver->resolve($type);
|
||||
$phpdocType = $this->resolveSelf($phpdocType, $refMethod->getDeclaringClass());
|
||||
if (! $phpdocType instanceof Object_) {
|
||||
throw MissingTypeHintException::invalidReturnType($refMethod);
|
||||
}
|
||||
|
||||
return $phpdocType->getFqsen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves "self" types into the class type.
|
||||
*/
|
||||
private function resolveSelf(Type $type, ReflectionClass $reflectionClass): Type
|
||||
{
|
||||
if ($type instanceof Self_) {
|
||||
return new Object_(new Fqsen('\\' . $reflectionClass->getName()));
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Hyperf\Di\Annotation\AnnotationCollector;
|
||||
use Hyperf\GraphQL\Annotation\Mutation;
|
||||
use Hyperf\GraphQL\Annotation\Query;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\QueryField;
|
||||
use TheCodingMachine\GraphQLite\QueryProviderInterface;
|
||||
|
||||
class QueryProvider implements QueryProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var FieldsBuilderFactory
|
||||
*/
|
||||
private $fieldsBuilderFactory;
|
||||
|
||||
/**
|
||||
* @var RecursiveTypeMapperInterface
|
||||
*/
|
||||
private $recursiveTypeMapper;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
public function __construct(FieldsBuilderFactory $fieldsBuilderFactory, RecursiveTypeMapperInterface $recursiveTypeMapper, ContainerInterface $container)
|
||||
{
|
||||
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
|
||||
$this->recursiveTypeMapper = $recursiveTypeMapper;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryField[]
|
||||
*/
|
||||
public function getQueries(): array
|
||||
{
|
||||
$queryList = [];
|
||||
$classes = AnnotationCollector::getMethodsByAnnotation(Query::class);
|
||||
$classes = array_unique(array_column($classes, 'class'));
|
||||
foreach ($classes as $className) {
|
||||
$fieldsBuilder = $this->fieldsBuilderFactory->buildFieldsBuilder($this->recursiveTypeMapper);
|
||||
$queryList = array_merge($queryList, $fieldsBuilder->getQueries($this->container->get($className)));
|
||||
}
|
||||
return $queryList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryField[]
|
||||
*/
|
||||
public function getMutations(): array
|
||||
{
|
||||
$mutationList = [];
|
||||
$classes = AnnotationCollector::getMethodsByAnnotation(Mutation::class);
|
||||
$classes = array_unique(array_column($classes, 'class'));
|
||||
foreach ($classes as $className) {
|
||||
$fieldsBuilder = $this->fieldsBuilderFactory->buildFieldsBuilder($this->recursiveTypeMapper);
|
||||
$mutationList = array_merge($mutationList, $fieldsBuilder->getMutations($this->container->get($className)));
|
||||
}
|
||||
return $mutationList;
|
||||
}
|
||||
}
|
55
src/graphql/src/Reader.php
Normal file
55
src/graphql/src/Reader.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Hyperf\Di\Annotation\AnnotationReader as HyperfAnnotationReader;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionProperty;
|
||||
|
||||
class Reader implements \Doctrine\Common\Annotations\Reader
|
||||
{
|
||||
public function __construct(protected HyperfAnnotationReader $reader)
|
||||
{
|
||||
}
|
||||
|
||||
public function getClassAnnotations(ReflectionClass $class)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getClassAnnotation(ReflectionClass $class, $annotationName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMethodAnnotations(ReflectionMethod $method)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getPropertyAnnotations(ReflectionProperty $property)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
|
||||
class ReaderFactory
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
AnnotationRegistry::registerLoader('class_exists');
|
||||
$doctrineAnnotationReader = new AnnotationReader();
|
||||
|
||||
if (function_exists('apcu_fetch')) {
|
||||
$doctrineAnnotationReader = new CachedReader($doctrineAnnotationReader, new ApcuCache(), true);
|
||||
}
|
||||
|
||||
return $doctrineAnnotationReader;
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Lock\Factory as LockFactory;
|
||||
use Symfony\Component\Lock\Store\FlockStore;
|
||||
use TheCodingMachine\GraphQLite\Mappers\CompositeTypeMapper;
|
||||
use TheCodingMachine\GraphQLite\Mappers\PorpaginasTypeMapper;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapper;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\TypeRegistry;
|
||||
|
||||
class RecursiveTypeMapperFactory
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
/**
|
||||
* @var TypeRegistry
|
||||
*/
|
||||
private $typeRegistry;
|
||||
|
||||
public function __construct(ContainerInterface $container, CacheInterface $cache, NamingStrategyInterface $namingStrategy, TypeRegistry $typeRegistry)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->container = $container;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
$this->typeRegistry = $typeRegistry;
|
||||
}
|
||||
|
||||
public function __invoke()
|
||||
{
|
||||
$annotationReader = new AnnotationReader($this->container->get(Reader::class), AnnotationReader::LAX_MODE);
|
||||
$typeGenerator = $this->container->get(TypeGenerator::class);
|
||||
$inputTypeGenerator = $this->container->get(InputTypeGenerator::class);
|
||||
$inputTypeUtils = $this->container->get(InputTypeUtils::class);
|
||||
$namingStrategy = $this->container->get(NamingStrategyInterface::class);
|
||||
$lockStore = new FlockStore(sys_get_temp_dir());
|
||||
$lockFactory = new LockFactory($lockStore);
|
||||
|
||||
$typeMappers[] = new TypeMapper(
|
||||
'app',
|
||||
$typeGenerator,
|
||||
$inputTypeGenerator,
|
||||
$inputTypeUtils,
|
||||
$this->container,
|
||||
$annotationReader,
|
||||
$namingStrategy,
|
||||
$lockFactory,
|
||||
$this->cache
|
||||
);
|
||||
$typeMappers[] = new PorpaginasTypeMapper();
|
||||
$compositeTypeMapper = new CompositeTypeMapper($typeMappers);
|
||||
return new RecursiveTypeMapper($compositeTypeMapper, $this->namingStrategy, $this->cache, $this->typeRegistry);
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use ReflectionMethod;
|
||||
use TheCodingMachine\GraphQLite\GraphQLException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\ArgumentResolver;
|
||||
use TheCodingMachine\GraphQLite\Types\ResolvableInputInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\ResolvableInputObjectType as TheCodingMachineResolvableInputObjectType;
|
||||
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* A GraphQL input object that can be resolved using a factory.
|
||||
*/
|
||||
class ResolvableInputObjectType extends TheCodingMachineResolvableInputObjectType implements ResolvableInputInterface
|
||||
{
|
||||
/**
|
||||
* @var ArgumentResolver
|
||||
*/
|
||||
private $argumentResolver;
|
||||
|
||||
/**
|
||||
* @var array<int, object|string>|callable
|
||||
*/
|
||||
private $resolve;
|
||||
|
||||
/**
|
||||
* QueryField constructor.
|
||||
* @param object|string $factory
|
||||
*/
|
||||
public function __construct(string $name, FieldsBuilderFactory $controllerQueryProviderFactory, RecursiveTypeMapperInterface $recursiveTypeMapper, $factory, string $methodName, ArgumentResolver $argumentResolver, ?string $comment, array $additionalConfig = [])
|
||||
{
|
||||
$this->argumentResolver = $argumentResolver;
|
||||
$this->resolve = [$factory, $methodName];
|
||||
|
||||
$fields = function () use ($controllerQueryProviderFactory, $factory, $methodName, $recursiveTypeMapper) {
|
||||
$method = new ReflectionMethod($factory, $methodName);
|
||||
$fieldProvider = $controllerQueryProviderFactory->buildFieldsBuilder($recursiveTypeMapper);
|
||||
return $fieldProvider->getInputFields($method);
|
||||
};
|
||||
|
||||
$config = [
|
||||
'name' => $name,
|
||||
'fields' => $fields,
|
||||
];
|
||||
if ($comment) {
|
||||
$config['description'] = $comment;
|
||||
}
|
||||
|
||||
$config += $additionalConfig;
|
||||
InputObjectType::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
public function resolve(array $args)
|
||||
{
|
||||
$toPassArgs = [];
|
||||
foreach ($this->getFields() as $name => $field) {
|
||||
$type = $field->getType();
|
||||
if (isset($args[$name])) {
|
||||
$val = $this->argumentResolver->resolve($args[$name], $type);
|
||||
} elseif ($field->defaultValueExists()) {
|
||||
$val = $field->defaultValue;
|
||||
} else {
|
||||
throw new GraphQLException("Expected argument '{$name}' was not provided in GraphQL input type '" . $this->name . "' used in factory '" . get_class($this->resolve[0]) . '::' . $this->resolve[1] . "()'");
|
||||
}
|
||||
|
||||
$toPassArgs[] = $val;
|
||||
}
|
||||
|
||||
$resolve = $this->resolve;
|
||||
|
||||
return $resolve(...$toPassArgs);
|
||||
}
|
||||
}
|
30
src/graphql/src/SchemaFactory.php
Normal file
30
src/graphql/src/SchemaFactory.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use TheCodingMachine\GraphQLite\SchemaFactory as BaseSchemaFactory;
|
||||
|
||||
class SchemaFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$factory = new BaseSchemaFactory($container->get(CacheInterface::class), $container);
|
||||
$factory->addTypeNamespace('App');
|
||||
$factory->addControllerNamespace('App');
|
||||
$factory->setDoctrineAnnotationReader($container->get(Reader::class));
|
||||
|
||||
return $factory->createSchema();
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\MutableObjectType;
|
||||
|
||||
/**
|
||||
* An object type built from the Type annotation.
|
||||
*/
|
||||
class TypeAnnotatedObjectType extends MutableObjectType
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $className;
|
||||
|
||||
public function __construct(string $className, array $config)
|
||||
{
|
||||
$this->className = $className;
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
public static function createFromAnnotatedClass(string $typeName, string $className, $annotatedObject, FieldsBuilderFactory $fieldsBuilderFactory, RecursiveTypeMapperInterface $recursiveTypeMapper): self
|
||||
{
|
||||
return new self($className, [
|
||||
'name' => $typeName,
|
||||
'fields' => function () use ($annotatedObject, $recursiveTypeMapper, $className, $fieldsBuilderFactory) {
|
||||
$parentClass = get_parent_class($className);
|
||||
$parentType = null;
|
||||
if ($parentClass !== false) {
|
||||
if ($recursiveTypeMapper->canMapClassToType($parentClass)) {
|
||||
$parentType = $recursiveTypeMapper->mapClassToType($parentClass, null);
|
||||
}
|
||||
}
|
||||
|
||||
$fieldProvider = $fieldsBuilderFactory->buildFieldsBuilder($recursiveTypeMapper);
|
||||
if ($annotatedObject !== null) {
|
||||
$fields = $fieldProvider->getFields($annotatedObject);
|
||||
} else {
|
||||
$fields = $fieldProvider->getSelfFields($className);
|
||||
}
|
||||
if ($parentType !== null) {
|
||||
$finalFields = $parentType->getFields();
|
||||
foreach ($fields as $name => $field) {
|
||||
$finalFields[$name] = $field;
|
||||
}
|
||||
return $finalFields;
|
||||
}
|
||||
return $fields;
|
||||
},
|
||||
'interfaces' => function () use ($className, $recursiveTypeMapper) {
|
||||
return $recursiveTypeMapper->findInterfaces($className);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
public function getMappedClassName(): string
|
||||
{
|
||||
return $this->className;
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\MissingAnnotationException;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\TypeRegistry;
|
||||
use TheCodingMachine\GraphQLite\Types\MutableObjectType;
|
||||
|
||||
/**
|
||||
* This class is in charge of creating Webonyx GraphQL types from annotated objects that do not extend the
|
||||
* Webonyx ObjectType class.
|
||||
*/
|
||||
class TypeGenerator
|
||||
{
|
||||
/**
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $annotationReader;
|
||||
|
||||
/**
|
||||
* @var FieldsBuilderFactory
|
||||
*/
|
||||
private $fieldsBuilderFactory;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
/**
|
||||
* @var TypeRegistry
|
||||
*/
|
||||
private $typeRegistry;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
public function __construct(
|
||||
AnnotationReader $annotationReader,
|
||||
FieldsBuilderFactory $fieldsBuilderFactory,
|
||||
NamingStrategyInterface $namingStrategy,
|
||||
TypeRegistry $typeRegistry,
|
||||
ContainerInterface $container
|
||||
) {
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->fieldsBuilderFactory = $fieldsBuilderFactory;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
$this->typeRegistry = $typeRegistry;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $annotatedObjectClassName the FQCN of an object with a Type annotation
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function mapAnnotatedObject(string $annotatedObjectClassName, RecursiveTypeMapperInterface $recursiveTypeMapper): MutableObjectType
|
||||
{
|
||||
$refTypeClass = new ReflectionClass($annotatedObjectClassName);
|
||||
|
||||
$typeField = $this->annotationReader->getTypeAnnotation($refTypeClass);
|
||||
|
||||
if ($typeField === null) {
|
||||
throw MissingAnnotationException::missingTypeException();
|
||||
}
|
||||
|
||||
$typeName = $this->namingStrategy->getOutputTypeName($refTypeClass->getName(), $typeField);
|
||||
|
||||
if ($this->typeRegistry->hasType($typeName)) {
|
||||
return $this->typeRegistry->getMutableObjectType($typeName);
|
||||
}
|
||||
|
||||
if (! $typeField->isSelfType()) {
|
||||
$annotatedObject = $this->container->get($annotatedObjectClassName);
|
||||
} else {
|
||||
$annotatedObject = null;
|
||||
}
|
||||
|
||||
return TypeAnnotatedObjectType::createFromAnnotatedClass($typeName, $typeField->getClass(), $annotatedObject, $this->fieldsBuilderFactory, $recursiveTypeMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $annotatedObject an object with a ExtendType annotation
|
||||
*/
|
||||
public function extendAnnotatedObject($annotatedObject, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper)
|
||||
{
|
||||
$refTypeClass = new ReflectionClass($annotatedObject);
|
||||
|
||||
$extendTypeAnnotation = $this->annotationReader->getExtendTypeAnnotation($refTypeClass);
|
||||
|
||||
if ($extendTypeAnnotation === null) {
|
||||
throw MissingAnnotationException::missingExtendTypeException();
|
||||
}
|
||||
|
||||
$type->addFields(function () use ($annotatedObject, $recursiveTypeMapper) {
|
||||
$fieldProvider = $this->fieldsBuilderFactory->buildFieldsBuilder($recursiveTypeMapper);
|
||||
return $fieldProvider->getFields($annotatedObject);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,865 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\GraphQL;
|
||||
|
||||
use GraphQL\Type\Definition\InputObjectType;
|
||||
use GraphQL\Type\Definition\OutputType;
|
||||
use Hyperf\GraphQL\Annotation\Type;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use Symfony\Component\Lock\Factory as LockFactory;
|
||||
use Symfony\Component\Lock\Lock;
|
||||
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
|
||||
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\CannotMapTypeExceptionInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\DuplicateMappingException;
|
||||
use TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\Mappers\TypeMapperInterface;
|
||||
use TheCodingMachine\GraphQLite\NamingStrategyInterface;
|
||||
use TheCodingMachine\GraphQLite\Types\MutableObjectType;
|
||||
|
||||
use function array_keys;
|
||||
use function class_exists;
|
||||
use function filemtime;
|
||||
|
||||
/**
|
||||
* Scans all the classes in a given namespace of the main project (not the vendor directory).
|
||||
* Analyzes all classes and uses the Type annotation to find the types automatically.
|
||||
*
|
||||
* Assumes that the container contains a class whose identifier is the same as the class name.
|
||||
*/
|
||||
class TypeMapper implements TypeMapperInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $annotationReader;
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var null|int
|
||||
*/
|
||||
private $globTtl;
|
||||
|
||||
/**
|
||||
* @var array<string,string> Maps a domain class to the GraphQL type annotated class
|
||||
*/
|
||||
private $mapClassToTypeArray = [];
|
||||
|
||||
/**
|
||||
* @var array<string,array<string,string>> Maps a domain class to one or many type extenders (with the ExtendType annotation) The array of type extenders has a key and value equals to FQCN
|
||||
*/
|
||||
private $mapClassToExtendTypeArray = [];
|
||||
|
||||
/**
|
||||
* @var array<string,string> Maps a GraphQL type name to the GraphQL type annotated class
|
||||
*/
|
||||
private $mapNameToType = [];
|
||||
|
||||
/**
|
||||
* @var array<string,array<string,string>> Maps a GraphQL type name to one or many type extenders (with the ExtendType annotation) The array of type extenders has a key and value equals to FQCN
|
||||
*/
|
||||
private $mapNameToExtendType = [];
|
||||
|
||||
/**
|
||||
* @var array<string,string[]> Maps a domain class to the factory method that creates the input type in the form [classname, methodname]
|
||||
*/
|
||||
private $mapClassToFactory = [];
|
||||
|
||||
/**
|
||||
* @var array<string,string[]> Maps a GraphQL input type name to the factory method that creates the input type in the form [classname, methodname]
|
||||
*/
|
||||
private $mapInputNameToFactory = [];
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var TypeGenerator
|
||||
*/
|
||||
private $typeGenerator;
|
||||
|
||||
/**
|
||||
* @var null|int
|
||||
*/
|
||||
private $mapTtl;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fullMapComputed = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fullMapClassToExtendTypeArrayComputed = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fullMapNameToExtendTypeArrayComputed = false;
|
||||
|
||||
/**
|
||||
* @var NamingStrategyInterface
|
||||
*/
|
||||
private $namingStrategy;
|
||||
|
||||
/**
|
||||
* @var InputTypeGenerator
|
||||
*/
|
||||
private $inputTypeGenerator;
|
||||
|
||||
/**
|
||||
* @var InputTypeUtils
|
||||
*/
|
||||
private $inputTypeUtils;
|
||||
|
||||
/**
|
||||
* The array of globbed classes.
|
||||
* Only instantiable classes are returned.
|
||||
* Key: fully qualified class name.
|
||||
*
|
||||
* @var array<string,ReflectionClass>
|
||||
*/
|
||||
private $classes;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $recursive;
|
||||
|
||||
/**
|
||||
* @var LockFactory
|
||||
*/
|
||||
private $lockFactory;
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->typeGenerator = $typeGenerator;
|
||||
$this->container = $container;
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->namingStrategy = $namingStrategy;
|
||||
$this->cache = $cache;
|
||||
$this->globTtl = $globTtl;
|
||||
$this->mapTtl = $mapTtl;
|
||||
$this->inputTypeGenerator = $inputTypeGenerator;
|
||||
$this->inputTypeUtils = $inputTypeUtils;
|
||||
$this->recursive = $recursive;
|
||||
$this->lockFactory = $lockFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type mapper can map the $className FQCN to a GraphQL type.
|
||||
*/
|
||||
public function canMapClassToType(string $className): bool
|
||||
{
|
||||
$typeClassName = $this->getTypeFromCacheByObjectClass($className);
|
||||
|
||||
if ($typeClassName !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$map = $this->getMapClassToType();
|
||||
|
||||
return isset($map[$className]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a PHP fully qualified class name to a GraphQL type.
|
||||
*
|
||||
* @param string $className the exact class name to look for (this function does not look into parent classes)
|
||||
* @param null|OutputType $subType an optional sub-type if the main class is an iterator that needs to be typed
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
*/
|
||||
public function mapClassToType(string $className, ?OutputType $subType, RecursiveTypeMapperInterface $recursiveTypeMapper): MutableObjectType
|
||||
{
|
||||
$typeClassName = $this->getTypeFromCacheByObjectClass($className);
|
||||
|
||||
if ($typeClassName === null) {
|
||||
$map = $this->getMapClassToType();
|
||||
if (! isset($map[$className])) {
|
||||
throw CannotMapTypeException::createForType($className);
|
||||
}
|
||||
$typeClassName = $map[$className];
|
||||
}
|
||||
|
||||
return $this->typeGenerator->mapAnnotatedObject($typeClassName, $recursiveTypeMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of classes that have matching input GraphQL types.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedClasses(): array
|
||||
{
|
||||
return array_keys($this->getMapClassToType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type mapper can map the $className FQCN to a GraphQL input type.
|
||||
*/
|
||||
public function canMapClassToInputType(string $className): bool
|
||||
{
|
||||
$factory = $this->getFactoryFromCacheByObjectClass($className);
|
||||
|
||||
if ($factory !== null) {
|
||||
return true;
|
||||
}
|
||||
$map = $this->getMapClassToFactory();
|
||||
return isset($map[$className]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a PHP fully qualified class name to a GraphQL input type.
|
||||
*
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
*/
|
||||
public function mapClassToInputType(string $className, RecursiveTypeMapperInterface $recursiveTypeMapper): InputObjectType
|
||||
{
|
||||
$factory = $this->getFactoryFromCacheByObjectClass($className);
|
||||
|
||||
if ($factory === null) {
|
||||
$map = $this->getMapClassToFactory();
|
||||
if (! isset($map[$className])) {
|
||||
throw CannotMapTypeException::createForInputType($className);
|
||||
}
|
||||
$factory = $map[$className];
|
||||
}
|
||||
|
||||
return $this->inputTypeGenerator->mapFactoryMethod($factory[0], $factory[1], $recursiveTypeMapper, $this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GraphQL type by name (can be either an input or output type).
|
||||
*
|
||||
* @param string $typeName The name of the GraphQL type
|
||||
* @return \GraphQL\Type\Definition\Type&(InputType|OutputType)
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function mapNameToType(string $typeName, RecursiveTypeMapperInterface $recursiveTypeMapper): \GraphQL\Type\Definition\Type
|
||||
{
|
||||
$typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
|
||||
if ($typeClassName === null) {
|
||||
$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
|
||||
if ($factory === null) {
|
||||
$mapNameToType = $this->getMapNameToType();
|
||||
if (isset($mapNameToType[$typeName])) {
|
||||
$typeClassName = $mapNameToType[$typeName];
|
||||
} else {
|
||||
$mapInputNameToFactory = $this->getMapInputNameToFactory();
|
||||
if (isset($mapInputNameToFactory[$typeName])) {
|
||||
$factory = $mapInputNameToFactory[$typeName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($typeClassName)) {
|
||||
return $this->typeGenerator->mapAnnotatedObject($typeClassName, $recursiveTypeMapper);
|
||||
}
|
||||
if (isset($factory)) {
|
||||
return $this->inputTypeGenerator->mapFactoryMethod($factory[0], $factory[1], $recursiveTypeMapper, $this->container);
|
||||
}
|
||||
|
||||
throw CannotMapTypeException::createForName($typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type mapper can map the $typeName GraphQL name to a GraphQL type.
|
||||
*
|
||||
* @param string $typeName The name of the GraphQL type
|
||||
*/
|
||||
public function canMapNameToType(string $typeName): bool
|
||||
{
|
||||
$typeClassName = $this->getTypeFromCacheByGraphQLTypeName($typeName);
|
||||
|
||||
if ($typeClassName !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
|
||||
if ($factory !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->getMaps();
|
||||
|
||||
return isset($this->mapNameToType[$typeName]) || isset($this->mapInputNameToFactory[$typeName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type mapper can extend an existing type for the $className FQCN.
|
||||
*/
|
||||
public function canExtendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
|
||||
{
|
||||
$extendTypeClassName = $this->getExtendTypesFromCacheByObjectClass($className);
|
||||
|
||||
if ($extendTypeClassName === null) {
|
||||
$map = $this->getMapClassToExtendTypeArray();
|
||||
}
|
||||
|
||||
return isset($this->mapClassToExtendTypeArray[$className]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the existing GraphQL type that is mapped to $className.
|
||||
*
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
*/
|
||||
public function extendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
|
||||
{
|
||||
$extendTypeClassNames = $this->getExtendTypesFromCacheByObjectClass($className);
|
||||
|
||||
if ($extendTypeClassNames === null) {
|
||||
$this->getMapClassToExtendTypeArray();
|
||||
}
|
||||
|
||||
if (! isset($this->mapClassToExtendTypeArray[$className])) {
|
||||
throw CannotMapTypeException::createForExtendType($className, $type);
|
||||
}
|
||||
|
||||
foreach ($this->mapClassToExtendTypeArray[$className] as $extendedTypeClass) {
|
||||
$this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type mapper can extend an existing type for the $typeName GraphQL type.
|
||||
*/
|
||||
public function canExtendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
|
||||
{
|
||||
$typeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
|
||||
|
||||
if ($typeClassNames !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
|
||||
if ($factory !== null) {
|
||||
return true;
|
||||
}*/
|
||||
|
||||
$map = $this->getMapNameToExtendType($recursiveTypeMapper);
|
||||
|
||||
return isset($map[$typeName])/* || isset($this->mapInputNameToFactory[$typeName]) */;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the existing GraphQL type that is mapped to the $typeName GraphQL type.
|
||||
*
|
||||
* @throws CannotMapTypeExceptionInterface
|
||||
*/
|
||||
public function extendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
|
||||
{
|
||||
$extendTypeClassNames = $this->getExtendTypesFromCacheByGraphQLTypeName($typeName);
|
||||
if ($extendTypeClassNames === null) {
|
||||
/*$factory = $this->getFactoryFromCacheByGraphQLInputTypeName($typeName);
|
||||
if ($factory === null) {*/
|
||||
$map = $this->getMapNameToExtendType($recursiveTypeMapper);
|
||||
if (! isset($map[$typeName])) {
|
||||
throw CannotMapTypeException::createForExtendName($typeName, $type);
|
||||
}
|
||||
$extendTypeClassNames = $map[$typeName];
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
foreach ($extendTypeClassNames as $extendedTypeClass) {
|
||||
$this->typeGenerator->extendAnnotatedObject($this->container->get($extendedTypeClass), $type, $recursiveTypeMapper);
|
||||
}
|
||||
|
||||
/*if (isset($this->mapInputNameToFactory[$typeName])) {
|
||||
$factory = $this->mapInputNameToFactory[$typeName];
|
||||
return $this->inputTypeGenerator->mapFactoryMethod($this->container->get($factory[0]), $factory[1], $recursiveTypeMapper);
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of fully qualified class names.
|
||||
*
|
||||
* @return array<string, array<string,string>>
|
||||
*/
|
||||
private function getMaps(): array
|
||||
{
|
||||
if ($this->fullMapComputed === false) {
|
||||
$namespace = str_replace('\\', '_', $this->namespace);
|
||||
$keyClassCache = 'globTypeMapper_' . $namespace;
|
||||
$keyNameCache = 'globTypeMapper_names_' . $namespace;
|
||||
$keyInputClassCache = 'globInputTypeMapper_' . $namespace;
|
||||
$keyInputNameCache = 'globInputTypeMapper_names_' . $namespace;
|
||||
$this->mapClassToTypeArray = $this->cache->get($keyClassCache);
|
||||
$this->mapNameToType = $this->cache->get($keyNameCache);
|
||||
$this->mapClassToFactory = $this->cache->get($keyInputClassCache);
|
||||
$this->mapInputNameToFactory = $this->cache->get($keyInputNameCache);
|
||||
if ($this->mapClassToTypeArray === null
|
||||
|| $this->mapNameToType === null
|
||||
|| $this->mapClassToFactory === null
|
||||
|| $this->mapInputNameToFactory === null
|
||||
) {
|
||||
$lock = $this->lockFactory->createLock('buildmap_' . $this->namespace, 5);
|
||||
if (! $lock->acquire()) {
|
||||
// Lock is being held right now. Generation is happening.
|
||||
// Let's wait and fetch the result from the cache.
|
||||
$lock->acquire(true);
|
||||
$lock->release();
|
||||
return $this->getMaps();
|
||||
}
|
||||
try {
|
||||
$this->buildMap();
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
// This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
|
||||
// Defaults to 2 seconds.
|
||||
$this->cache->set($keyClassCache, $this->mapClassToTypeArray, $this->globTtl);
|
||||
$this->cache->set($keyNameCache, $this->mapNameToType, $this->globTtl);
|
||||
$this->cache->set($keyInputClassCache, $this->mapClassToFactory, $this->globTtl);
|
||||
$this->cache->set($keyInputNameCache, $this->mapInputNameToFactory, $this->globTtl);
|
||||
}
|
||||
$this->fullMapComputed = true;
|
||||
}
|
||||
return [
|
||||
'mapClassToTypeArray' => $this->mapClassToTypeArray,
|
||||
'mapNameToType' => $this->mapNameToType,
|
||||
'mapClassToFactory' => $this->mapClassToFactory,
|
||||
'mapInputNameToFactory' => $this->mapInputNameToFactory,
|
||||
];
|
||||
}
|
||||
|
||||
private function getMapClassToType(): array
|
||||
{
|
||||
return $this->getMaps()['mapClassToTypeArray'];
|
||||
}
|
||||
|
||||
private function getMapNameToType(): array
|
||||
{
|
||||
return $this->getMaps()['mapNameToType'];
|
||||
}
|
||||
|
||||
private function getMapClassToFactory(): array
|
||||
{
|
||||
return $this->getMaps()['mapClassToFactory'];
|
||||
}
|
||||
|
||||
private function getMapInputNameToFactory(): array
|
||||
{
|
||||
return $this->getMaps()['mapInputNameToFactory'];
|
||||
}
|
||||
|
||||
private function getMapClassToExtendTypeArray(): array
|
||||
{
|
||||
if ($this->fullMapClassToExtendTypeArrayComputed === false) {
|
||||
$namespace = str_replace('\\', '_', $this->namespace);
|
||||
$keyExtendClassCache = 'globTypeMapperExtend_' . $namespace;
|
||||
$this->mapClassToExtendTypeArray = $this->cache->get($keyExtendClassCache);
|
||||
if ($this->mapClassToExtendTypeArray === null) {
|
||||
$lock = $this->lockFactory->createLock('buildmapclassextend_' . $this->namespace, 5);
|
||||
if (! $lock->acquire()) {
|
||||
// Lock is being held right now. Generation is happening.
|
||||
// Let's wait and fetch the result from the cache.
|
||||
$lock->acquire(true);
|
||||
$lock->release();
|
||||
return $this->getMapClassToExtendTypeArray();
|
||||
}
|
||||
$lock->acquire(true);
|
||||
try {
|
||||
$this->buildMapClassToExtendTypeArray($lock);
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
// This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
|
||||
// Defaults to 2 seconds.
|
||||
$this->cache->set($keyExtendClassCache, $this->mapClassToExtendTypeArray, $this->globTtl);
|
||||
}
|
||||
$this->fullMapClassToExtendTypeArrayComputed = true;
|
||||
}
|
||||
return $this->mapClassToExtendTypeArray;
|
||||
}
|
||||
|
||||
private function getMapNameToExtendType(RecursiveTypeMapperInterface $recursiveTypeMapper): array
|
||||
{
|
||||
if ($this->fullMapNameToExtendTypeArrayComputed === false) {
|
||||
$namespace = str_replace('\\', '_', $this->namespace);
|
||||
$keyExtendNameCache = 'globTypeMapperExtend_names_' . $namespace;
|
||||
$this->mapNameToExtendType = $this->cache->get($keyExtendNameCache);
|
||||
if ($this->mapNameToExtendType === null) {
|
||||
$lock = $this->lockFactory->createLock('buildmapnameextend_' . $this->namespace, 5);
|
||||
if (! $lock->acquire()) {
|
||||
// Lock is being held right now. Generation is happening.
|
||||
// Let's wait and fetch the result from the cache.
|
||||
$lock->acquire(true);
|
||||
$lock->release();
|
||||
return $this->getMapNameToExtendType($recursiveTypeMapper);
|
||||
}
|
||||
$lock->acquire(true);
|
||||
try {
|
||||
$this->buildMapNameToExtendTypeArray($recursiveTypeMapper);
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
// This is a very short lived cache. Useful to avoid overloading a server in case of heavy load.
|
||||
// Defaults to 2 seconds.
|
||||
$this->cache->set($keyExtendNameCache, $this->mapNameToExtendType, $this->globTtl);
|
||||
}
|
||||
$this->fullMapNameToExtendTypeArrayComputed = true;
|
||||
}
|
||||
return $this->mapNameToExtendType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of globbed classes.
|
||||
* Only instantiable classes are returned.
|
||||
*
|
||||
* @return array<string,ReflectionClass> Key: fully qualified class name
|
||||
*/
|
||||
private function getClassList(): array
|
||||
{
|
||||
if ($this->classes === null) {
|
||||
$this->classes = [];
|
||||
$classes = ClassCollector::getClasses();
|
||||
foreach ($classes as $className) {
|
||||
if (! class_exists($className)) {
|
||||
continue;
|
||||
}
|
||||
$refClass = new ReflectionClass($className);
|
||||
if (! $refClass->isInstantiable()) {
|
||||
continue;
|
||||
}
|
||||
$this->classes[$className] = $refClass;
|
||||
}
|
||||
}
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
private function buildMap(): void
|
||||
{
|
||||
$this->mapClassToTypeArray = [];
|
||||
$this->mapNameToType = [];
|
||||
$this->mapClassToFactory = [];
|
||||
$this->mapInputNameToFactory = [];
|
||||
|
||||
/** @var ReflectionClass[] $classes */
|
||||
$classes = $this->getClassList();
|
||||
foreach ($classes as $className => $refClass) {
|
||||
$type = $this->annotationReader->getTypeAnnotation($refClass);
|
||||
|
||||
if ($type !== null) {
|
||||
if (isset($this->mapClassToTypeArray[$type->getClass()])) {
|
||||
throw DuplicateMappingException::createForType($type->getClass(), $this->mapClassToTypeArray[$type->getClass()], $className);
|
||||
}
|
||||
$this->storeTypeInCache($className, $type, $refClass->getFileName());
|
||||
}
|
||||
|
||||
$isAbstract = $refClass->isAbstract();
|
||||
|
||||
foreach ($refClass->getMethods() as $method) {
|
||||
if (! $method->isPublic() || ($isAbstract && ! $method->isStatic())) {
|
||||
continue;
|
||||
}
|
||||
$factory = $this->annotationReader->getFactoryAnnotation($method);
|
||||
if ($factory !== null) {
|
||||
[$inputName, $className] = $this->inputTypeUtils->getInputTypeNameAndClassName($method);
|
||||
|
||||
if (isset($this->mapClassToFactory[$className])) {
|
||||
throw DuplicateMappingException::createForFactory($className, $this->mapClassToFactory[$className][0], $this->mapClassToFactory[$className][1], $refClass->getName(), $method->name);
|
||||
}
|
||||
$this->storeInputTypeInCache($method, $inputName, $className, $refClass->getFileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function buildMapClassToExtendTypeArray(Lock $lock): void
|
||||
{
|
||||
$lock->acquire(true);
|
||||
try {
|
||||
$this->mapClassToExtendTypeArray = [];
|
||||
$classes = $this->getClassList();
|
||||
foreach ($classes as $className => $refClass) {
|
||||
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
|
||||
|
||||
if ($extendType !== null) {
|
||||
$this->storeExtendTypeMapperByClassInCache($className, $extendType, $refClass->getFileName());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
|
||||
private function buildMapNameToExtendTypeArray(RecursiveTypeMapperInterface $recursiveTypeMapper): void
|
||||
{
|
||||
$this->mapNameToExtendType = [];
|
||||
$classes = $this->getClassList();
|
||||
foreach ($classes as $className => $refClass) {
|
||||
$extendType = $this->annotationReader->getExtendTypeAnnotation($refClass);
|
||||
|
||||
if ($extendType !== null) {
|
||||
$this->storeExtendTypeMapperByNameInCache($className, $extendType, $refClass->getFileName(), $recursiveTypeMapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in cache the mapping TypeClass <=> Object class <=> GraphQL type name.
|
||||
*/
|
||||
private function storeTypeInCache(string $typeClassName, Type $type, string $typeFileName): void
|
||||
{
|
||||
$objectClassName = $type->getClass();
|
||||
$this->mapClassToTypeArray[$objectClassName] = $typeClassName;
|
||||
$this->cache->set('globTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $objectClassName), [
|
||||
'filemtime' => filemtime($typeFileName),
|
||||
'fileName' => $typeFileName,
|
||||
'typeClass' => $typeClassName,
|
||||
], $this->mapTtl);
|
||||
$typeName = $this->namingStrategy->getOutputTypeName($typeClassName, $type);
|
||||
$this->mapNameToType[$typeName] = $typeClassName;
|
||||
$this->cache->set('globTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $typeName, [
|
||||
'filemtime' => filemtime($typeFileName),
|
||||
'fileName' => $typeFileName,
|
||||
'typeClass' => $typeClassName,
|
||||
], $this->mapTtl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in cache the mapping between InputType name <=> Object class.
|
||||
*/
|
||||
private function storeInputTypeInCache(ReflectionMethod $refMethod, string $inputName, string $className, string $fileName): void
|
||||
{
|
||||
$refArray = [$refMethod->getDeclaringClass()->getName(), $refMethod->getName()];
|
||||
$this->mapClassToFactory[$className] = $refArray;
|
||||
$this->cache->set('globInputTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $className), [
|
||||
'filemtime' => filemtime($fileName),
|
||||
'fileName' => $fileName,
|
||||
'factory' => $refArray,
|
||||
], $this->mapTtl);
|
||||
$this->mapInputNameToFactory[$inputName] = $refArray;
|
||||
$this->cache->set('globInputTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $inputName, [
|
||||
'filemtime' => filemtime($fileName),
|
||||
'fileName' => $fileName,
|
||||
'factory' => $refArray,
|
||||
], $this->mapTtl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in cache the mapping ExtendTypeClass <=> Object class.
|
||||
*/
|
||||
private function storeExtendTypeMapperByClassInCache(string $extendTypeClassName, ExtendType $extendType, string $typeFileName): void
|
||||
{
|
||||
$objectClassName = $extendType->getClass();
|
||||
$this->mapClassToExtendTypeArray[$objectClassName][$extendTypeClassName] = $extendTypeClassName;
|
||||
$this->cache->set('globExtendTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $objectClassName), [
|
||||
'filemtime' => filemtime($typeFileName),
|
||||
'fileName' => $typeFileName,
|
||||
'extendTypeClasses' => $this->mapClassToExtendTypeArray[$objectClassName],
|
||||
], $this->mapTtl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in cache the mapping ExtendTypeClass <=> name class.
|
||||
*/
|
||||
private function storeExtendTypeMapperByNameInCache(string $extendTypeClassName, ExtendType $extendType, string $typeFileName, RecursiveTypeMapperInterface $recursiveTypeMapper): void
|
||||
{
|
||||
$targetType = $recursiveTypeMapper->mapClassToType($extendType->getClass(), null);
|
||||
$typeName = $targetType->name;
|
||||
|
||||
$this->mapNameToExtendType[$typeName][$extendTypeClassName] = $extendTypeClassName;
|
||||
$this->cache->set('globExtendTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $typeName, [
|
||||
'filemtime' => filemtime($typeFileName),
|
||||
'fileName' => $typeFileName,
|
||||
'extendTypeClasses' => $this->mapNameToExtendType[$typeName],
|
||||
], $this->mapTtl);
|
||||
}
|
||||
|
||||
private function getTypeFromCacheByObjectClass(string $className): ?string
|
||||
{
|
||||
if (isset($this->mapClassToTypeArray[$className])) {
|
||||
return $this->mapClassToTypeArray[$className];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $className));
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'typeClass' => $typeClassName
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapClassToTypeArray[$className] = $typeClassName;
|
||||
return $typeClassName;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getTypeFromCacheByGraphQLTypeName(string $graphqlTypeName): ?string
|
||||
{
|
||||
if (isset($this->mapNameToType[$graphqlTypeName])) {
|
||||
return $this->mapNameToType[$graphqlTypeName];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $graphqlTypeName);
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'typeClass' => $typeClassName
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapNameToType[$graphqlTypeName] = $typeClassName;
|
||||
return $typeClassName;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string[] A pointer to the factory [$className, $methodName] or null on cache miss
|
||||
*/
|
||||
private function getFactoryFromCacheByObjectClass(string $className): ?array
|
||||
{
|
||||
if (isset($this->mapClassToFactory[$className])) {
|
||||
return $this->mapClassToFactory[$className];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globInputTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $className));
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'factory' => $factory
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapClassToFactory[$className] = $factory;
|
||||
return $factory;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|array<string,string> An array of classes with the ExtendType annotation (key and value = FQCN)
|
||||
*/
|
||||
private function getExtendTypesFromCacheByObjectClass(string $className): ?array
|
||||
{
|
||||
if (isset($this->mapClassToExtendTypeArray[$className])) {
|
||||
return $this->mapClassToExtendTypeArray[$className];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globExtendTypeMapperByClass_' . str_replace('\\', '_', $this->namespace) . '_' . str_replace('\\', '_', $className));
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'extendTypeClasses' => $extendTypeClassNames
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapClassToExtendTypeArray[$className] = $extendTypeClassNames;
|
||||
return $extendTypeClassNames;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|array<string,string> An array of classes with the ExtendType annotation (key and value = FQCN)
|
||||
*/
|
||||
private function getExtendTypesFromCacheByGraphQLTypeName(string $graphqlTypeName): ?array
|
||||
{
|
||||
if (isset($this->mapNameToExtendType[$graphqlTypeName])) {
|
||||
return $this->mapNameToExtendType[$graphqlTypeName];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globExtendTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $graphqlTypeName);
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'extendTypeClasses' => $extendTypeClassNames
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapNameToExtendType[$graphqlTypeName] = $extendTypeClassNames;
|
||||
return $extendTypeClassNames;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string[] A pointer to the factory [$className, $methodName] or null on cache miss
|
||||
*/
|
||||
private function getFactoryFromCacheByGraphQLInputTypeName(string $graphqlTypeName): ?array
|
||||
{
|
||||
if (isset($this->mapInputNameToFactory[$graphqlTypeName])) {
|
||||
return $this->mapInputNameToFactory[$graphqlTypeName];
|
||||
}
|
||||
|
||||
// Let's try from the cache
|
||||
$item = $this->cache->get('globInputTypeMapperByName_' . str_replace('\\', '_', $this->namespace) . '_' . $graphqlTypeName);
|
||||
if ($item !== null) {
|
||||
[
|
||||
'filemtime' => $filemtime,
|
||||
'fileName' => $typeFileName,
|
||||
'factory' => $factory
|
||||
] = $item;
|
||||
|
||||
if ($filemtime === @filemtime($typeFileName)) {
|
||||
$this->mapInputNameToFactory[$graphqlTypeName] = $factory;
|
||||
return $factory;
|
||||
}
|
||||
}
|
||||
|
||||
// cache miss
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user