Skip to content

Commit

Permalink
Merge 8f22ee6 into 1cc83ff
Browse files Browse the repository at this point in the history
  • Loading branch information
devmaslov committed Jun 27, 2020
2 parents 1cc83ff + 8f22ee6 commit 1956406
Show file tree
Hide file tree
Showing 46 changed files with 1,387 additions and 206 deletions.
3 changes: 0 additions & 3 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ parameters:
ignoreErrors:
- "#PHPDoc tag \\@throws with type Psr\\\\Container\\\\ContainerExceptionInterface is not subtype of Throwable#"
#- "#Property TheCodingMachine\\\\GraphQLite\\\\Types\\\\ResolvableInputObjectType::\\$resolve \\(array<int, object\\|string>&callable\\) does not accept array<int,object\\|string>#"
- "#Variable \\$prefetchRefMethod might not be defined.#"
#- "#Parameter \\#2 \\$type of class TheCodingMachine\\\\GraphQLite\\\\Parameters\\\\InputTypeParameter constructor expects GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type, GraphQL\\\\Type\\\\Definition\\\\InputType\\|GraphQL\\\\Type\\\\Definition\\\\Type given.#"
- "#Parameter .* of class ReflectionMethod constructor expects string, object\\|string given.#"
-
Expand Down Expand Up @@ -31,8 +30,6 @@ parameters:
message: '#Method TheCodingMachine\\GraphQLite\\AnnotationReader::getMethodAnnotations\(\) should return array<int, T of object> but returns array<object>.#'
path: src/AnnotationReader.php
- '#Call to an undefined method GraphQL\\Error\\ClientAware::getMessage()#'
# Needed because of a bug in PHP-CS
- '#PHPDoc tag @param for parameter \$args with type mixed is not subtype of native type array<int, mixed>.#'

#-
# message: '#If condition is always true#'
Expand Down
99 changes: 90 additions & 9 deletions src/AnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use ReflectionClass;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use RuntimeException;
use TheCodingMachine\GraphQLite\Annotations\AbstractRequest;
use TheCodingMachine\GraphQLite\Annotations\Decorate;
Expand All @@ -19,6 +20,7 @@
use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException;
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
use TheCodingMachine\GraphQLite\Annotations\Factory;
use TheCodingMachine\GraphQLite\Annotations\Input;
use TheCodingMachine\GraphQLite\Annotations\MiddlewareAnnotationInterface;
use TheCodingMachine\GraphQLite\Annotations\MiddlewareAnnotations;
use TheCodingMachine\GraphQLite\Annotations\ParameterAnnotationInterface;
Expand Down Expand Up @@ -64,6 +66,15 @@ class AnnotationReader
*/
private $mode;

/** @var array<string, (object|null)> */
private $methodAnnotationCache = [];

/** @var array<string, array<object>> */
private $methodAnnotationsCache = [];

/** @var array<string, array<object>> */
private $propertyAnnotationsCache = [];

/**
* @param string $mode One of self::LAX_MODE or self::STRICT_MODE
* @param string[] $strictNamespaces
Expand Down Expand Up @@ -97,6 +108,30 @@ public function getTypeAnnotation(ReflectionClass $refClass): ?Type
return $type;
}

/**
* @param ReflectionClass<T> $refClass
*
* @return Input[]
*
* @throws AnnotationException
*
* @template T of object
*/
public function getInputAnnotations(ReflectionClass $refClass): array
{
try {
/** @var Input[] $inputs */
$inputs = $this->getClassAnnotations($refClass, Input::class);
foreach ($inputs as $input) {
$input->setClass($refClass->getName());
}
} catch (ClassNotFoundException $e) {
throw ClassNotFoundException::wrapException($e, $refClass->getName());
}

return $inputs;
}

/**
* @param ReflectionClass<T> $refClass
*
Expand Down Expand Up @@ -219,10 +254,18 @@ public function getParameterAnnotationsPerParameter(array $refParameters): array
}, $parameterAnnotationsPerParameter);
}

public function getMiddlewareAnnotations(ReflectionMethod $refMethod): MiddlewareAnnotations
/**
* @param ReflectionMethod|ReflectionProperty $reflection
*
* @throws AnnotationException
*/
public function getMiddlewareAnnotations($reflection): MiddlewareAnnotations
{
/** @var MiddlewareAnnotationInterface[] $middlewareAnnotations */
$middlewareAnnotations = $this->getMethodAnnotations($refMethod, MiddlewareAnnotationInterface::class);
if ($reflection instanceof ReflectionMethod) {
$middlewareAnnotations = $this->getMethodAnnotations($reflection, MiddlewareAnnotationInterface::class);
} else {
$middlewareAnnotations = $this->getPropertyAnnotations($reflection, MiddlewareAnnotationInterface::class);
}

return new MiddlewareAnnotations($middlewareAnnotations);
}
Expand Down Expand Up @@ -263,9 +306,6 @@ private function getClassAnnotation(ReflectionClass $refClass, string $annotatio
return $type;
}

/** @var array<string, (object|null)> */
private $methodAnnotationCache = [];

/**
* Returns a method annotation and handles correctly errors.
*/
Expand Down Expand Up @@ -352,9 +392,6 @@ public function getClassAnnotations(ReflectionClass $refClass, string $annotatio
return [];
}

/** @var array<string, array<object>> */
private $methodAnnotationsCache = [];

/**
* Returns the method's annotations.
*
Expand Down Expand Up @@ -396,6 +433,50 @@ public function getMethodAnnotations(ReflectionMethod $refMethod, string $annota
return $toAddAnnotations;
}

/**
* Returns the property's annotations.
*
* @param class-string<T> $annotationClass
*
* @return array<int, T>
*
* @throws AnnotationException
*
* @template T of object
*/
public function getPropertyAnnotations(ReflectionProperty $refProperty, string $annotationClass): array
{
$cacheKey = $refProperty->getDeclaringClass()->getName() . '::' . $refProperty->getName() . '_s_' . $annotationClass;
if (isset($this->propertyAnnotationsCache[$cacheKey])) {
/** @var array<int, T> $annotations */
$annotations = $this->propertyAnnotationsCache[$cacheKey];

return $annotations;
}

$toAddAnnotations = [];
try {
$allAnnotations = $this->reader->getPropertyAnnotations($refProperty);
$toAddAnnotations = array_filter($allAnnotations, static 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, $refProperty->getDocComment() ?: '', $refProperty->getDeclaringClass()->getName())) {
throw $e;
}
}
}

$this->propertyAnnotationsCache[$cacheKey] = $toAddAnnotations;

return $toAddAnnotations;
}

/**
* @param ReflectionClass<Enum> $refClass
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/FailWith.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/**
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
* @Attributes({
* @Attribute("value", type = "mixed"),
* @Attribute("mode", type = "string")
Expand Down
46 changes: 45 additions & 1 deletion src/Annotations/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,53 @@

namespace TheCodingMachine\GraphQLite\Annotations;

use Doctrine\Common\Annotations\Annotation\Attribute;

/**
* @Annotation
* @Target({"METHOD"})
* @Target({"PROPERTY", "METHOD"})
* @Attributes({
* @Attribute("name", type = "string"),
* @Attribute("outputType", type = "string"),
* @Attribute("prefetchMethod", type = "string"),
* @Attribute("for", type = "string[]"),
* @Attribute("description", type = "string"),
* @Attribute("inputType", type = "string"),
* })
*/
class Field extends AbstractRequest
{
/** @var string|null */
private $prefetchMethod;

/**
* Input/Output type names for which this fields should be applied to.
*
* @var string[]|null
*/
private $for = null;

/** @var string|null */
private $description;

/** @var string|null */
private $inputType;

/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->prefetchMethod = $attributes['prefetchMethod'] ?? null;
$this->description = $attributes['description'] ?? null;
$this->inputType = $attributes['inputType'] ?? null;

if (empty($attributes['for'])) {
return;
}

$this->for = (array) $attributes['for'];
}

/**
Expand All @@ -34,4 +60,22 @@ public function getPrefetchMethod(): ?string
{
return $this->prefetchMethod;
}

/**
* @return string[]|null
*/
public function getFor(): ?array
{
return $this->for;
}

public function getDescription(): ?string
{
return $this->description;
}

public function getInputType(): ?string
{
return $this->inputType;
}
}
2 changes: 1 addition & 1 deletion src/Annotations/HideIfUnauthorized.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* or has no right associated.
*
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
class HideIfUnauthorized implements MiddlewareAnnotationInterface
{
Expand Down
100 changes: 100 additions & 0 deletions src/Annotations/Input.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Annotations;

use Doctrine\Common\Annotations\Annotation\Attribute;
use RuntimeException;

/**
* The Input annotation must be put in a GraphQL input type class docblock and is used to map to the underlying PHP class
* this is exposed via this input type.
*
* @Annotation
* @Target({"CLASS"})
* @Attributes({
* @Attribute("name", type = "string"),
* @Attribute("default", type = "bool"),
* @Attribute("description", type = "string"),
* @Attribute("update", type = "bool"),
* })
*/
class Input
{
/** @var string|null */
private $class;

/** @var string|null */
private $name;

/** @var bool */
private $default;

/** @var string|null */
private $description;

/** @var bool */
private $update;

/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [])
{
$this->name = $attributes['name'] ?? null;
$this->default = $attributes['default'] ?? ! isset($attributes['name']);
$this->description = $attributes['description'] ?? null;
$this->update = $attributes['update'] ?? false;
}

/**
* Returns the fully qualified class name of the targeted class.
*/
public function getClass(): string
{
if ($this->class === null) {
throw new RuntimeException('Empty class for @Input annotation. You MUST create the Input annotation object using the GraphQLite AnnotationReader');
}

return $this->class;
}

public function setClass(string $class): void
{
$this->class = $class;
}

/**
* Returns the GraphQL input name for this type.
*/
public function getName(): ?string
{
return $this->name;
}

/**
* Returns true if this type should map the targeted class by default.
*/
public function isDefault(): bool
{
return $this->default;
}

/**
* Returns description about this input type.
*/
public function getDescription(): ?string
{
return $this->description;
}

/**
* Returns true if this type should behave as update resource.
* Such input type has all fields optional and without default value in the documentation.
*/
public function isUpdate(): bool
{
return $this->update;
}
}
2 changes: 1 addition & 1 deletion src/Annotations/Logged.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

/**
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
class Logged implements MiddlewareAnnotationInterface
{
Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/Right.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/**
* @Annotation
* @Target({"ANNOTATION", "METHOD"})
* @Target({"PROPERTY", "ANNOTATION", "METHOD"})
* @Attributes({
* @Attribute("name", type = "string"),
* })
Expand Down
2 changes: 1 addition & 1 deletion src/Annotations/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/**
* @Annotation
* @Target({"ANNOTATION", "METHOD"})
* @Target({"PROPERTY", "ANNOTATION", "METHOD"})
* @Attributes({
* @Attribute("expression", type = "string"),
* @Attribute("failWith", type = "mixed"),
Expand Down

0 comments on commit 1956406

Please sign in to comment.