Skip to content

Commit

Permalink
patch: drop DoctrineAnnotations (#677)
Browse files Browse the repository at this point in the history
* patch: drop DoctrineAnnotations

BC: AnnotationReader constructor changed and it's methods stops triggering exceptions

TestControllerWithInvalidParameterAnnotation deleted as `for` is ignored when `HideParameter` used as parameter attribute

Also attribute exceptions and classes polished

Fixes: #675

* fix: AnnotationReader condition

* test: improve coverage

* fix: drop parameters check

Currently, each parameter attribute is assigned to method's parameter instead of method, makes this check useless

* fix: code style

* fix: code review

* docs: updated with annotations removal

* fix: static analysis
  • Loading branch information
fogrye authored Apr 13, 2024
1 parent bcda966 commit 870b448
Show file tree
Hide file tree
Showing 194 changed files with 1,431 additions and 5,193 deletions.
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"require": {
"php": ">=8.1",
"ext-json": "*",
"doctrine/annotations": "^1.14 || ^2.0",
"composer/package-versions-deprecated": "^1.8",
"phpdocumentor/reflection-docblock": "^4.3 || ^5.0",
"phpdocumentor/type-resolver": "^1.4",
Expand Down
271 changes: 57 additions & 214 deletions src/AnnotationReader.php

Large diffs are not rendered by default.

35 changes: 15 additions & 20 deletions src/Annotations/Autowire.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,36 @@
use function ltrim;

/**
* Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.
*
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Attributes({
* @Attribute("for", type = "string"),
* @Attribute("identifier", type = "string")
* })
* Use this attribute to autowire a service from the container into a given parameter of a field/query/mutation.
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
class Autowire implements ParameterAnnotationInterface
{
/** @var string|null */
private $for;
/** @var string|null */
private $identifier;

/** @param array<string, mixed>|string $identifier */
public function __construct(array|string $identifier = [])
private string|null $for = null;
private string|null $identifier = null;

/** @param array<string, mixed>|string $params */
public function __construct(
array|string $params = [],
string|null $for = null,
string|null $identifier = null,
)
{
$values = $identifier;
$values = $params;
if (is_string($values)) {
$this->identifier = $values;
} else {
$this->identifier = $values['identifier'] ?? $values['value'] ?? null;
if (isset($values['for'])) {
$this->for = ltrim($values['for'], '$');
$this->identifier = $identifier ?? $values['identifier'] ?? $values['value'] ?? null;
if (isset($values['for']) || $for !== null) {
$this->for = ltrim($for ?? $values['for'], '$');
}
}
}

public function getTarget(): string
{
if ($this->for === null) {
throw new BadMethodCallException('The @Autowire annotation must be passed a target. For instance: "@Autowire(for="$myService")"');
throw new BadMethodCallException('The #[Autowire] attribute must be passed a target. For instance: "#[Autowire(for: "$myService")]"');
}
return $this->for;
}
Expand Down
13 changes: 3 additions & 10 deletions src/Annotations/Decorate.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@
use function is_string;

/**
* Methods with this annotation are decorating an input type when the input type is resolved.
* Methods with this attribute are decorating an input type when the input type is resolved.
* This is meant to be used only when the input type is provided by a third-party library and you want to modify it.
*
* @Annotation
* @Target({"METHOD"})
* @Attributes({
* @Attribute("inputTypeName", type = "string"),
* })
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Decorate
{
/** @var string */
private $inputTypeName;
private string $inputTypeName;

/**
* @param array<string, mixed>|string $inputTypeName
Expand All @@ -36,7 +29,7 @@ public function __construct(array|string $inputTypeName = [])
if (is_string($values)) {
$this->inputTypeName = $values;
} elseif (! isset($values['value']) && ! isset($values['inputTypeName'])) {
throw new BadMethodCallException('The @Decorate annotation must be passed an input type. For instance: "@Decorate("MyInputType")"');
throw new BadMethodCallException('The #[Decorate] attribute must be passed an input type. For instance: "#[Decorate("MyInputType")]"');
} else {
$this->inputTypeName = $values['value'] ?? $values['inputTypeName'];
}
Expand Down
4 changes: 2 additions & 2 deletions src/Annotations/Exceptions/ClassNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public static function couldNotFindClass(string $className): self

public static function wrapException(self $e, string $className): self
{
return new self($e->getMessage() . " defined in @Type annotation of class '" . $className . "'");
return new self($e->getMessage() . " defined in #[Type] attribute of class '" . $className . "'");
}

public static function wrapExceptionForExtendTag(self $e, string $className): self
{
return new self($e->getMessage() . " defined in @ExtendType annotation of class '" . $className . "'");
return new self($e->getMessage() . " defined in #[ExtendType] attribute of class '" . $className . "'");
}
}
7 changes: 1 addition & 6 deletions src/Annotations/Exceptions/InvalidParameterException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,8 @@

class InvalidParameterException extends BadMethodCallException
{
public static function parameterNotFound(string $parameter, string $annotationClass, ReflectionMethod $reflectionMethod): self
{
return new self(sprintf('Parameter "%s" declared in annotation "%s" of method "%s::%s()" does not exist.', $parameter, $annotationClass, $reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName()));
}

public static function parameterNotFoundFromSourceField(string $parameter, string $annotationClass, ReflectionMethod $reflectionMethod): self
{
return new self(sprintf('Could not find parameter "%s" declared in annotation "%s". This annotation is itself declared in a SourceField annotation targeting resolver "%s::%s()".', $parameter, $annotationClass, $reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName()));
return new self(sprintf('Could not find parameter "%s" declared in annotation "%s". This annotation is itself declared in a SourceField attribute targeting resolver "%s::%s()".', $parameter, $annotationClass, $reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName()));
}
}
11 changes: 2 additions & 9 deletions src/Annotations/ExtendType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,7 @@
use function ltrim;

/**
* The ExtendType annotation must be put in a GraphQL type class docblock and is used to add additional fields to the underlying PHP class.
*
* @Annotation
* @Target({"CLASS"})
* @Attributes({
* @Attribute("class", type = "string"),
* @Attribute("name", type = "string"),
* })
* The ExtendType attribute must be put in a GraphQL type class docblock and is used to add additional fields to the underlying PHP class.
*/
#[Attribute(Attribute::TARGET_CLASS)]
class ExtendType
Expand All @@ -41,7 +34,7 @@ public function __construct(array $attributes = [], string|null $class = null, s
$this->name = $name ?? $attributes['name'] ?? null;
$this->class = $className;
if (! $this->class && ! $this->name) {
throw new BadMethodCallException('In annotation @ExtendType, missing one of the compulsory parameter "class" or "name".');
throw new BadMethodCallException('In attribute #[ExtendType], missing one of the compulsory parameter "class" or "name".');
}
}

Expand Down
9 changes: 1 addition & 8 deletions src/Annotations/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@

/**
* Factories are methods used to declare GraphQL input types.
*
* @Annotation
* @Target({"METHOD"})
* @Attributes({
* @Attribute("name", type = "string"),
* @Attribute("default", type = "bool")
* })
*/
#[Attribute(Attribute::TARGET_METHOD)]
class Factory
Expand All @@ -33,7 +26,7 @@ public function __construct(array $attributes = [], string|null $name = null, bo
$this->default = $default ?? $attributes['default'] ?? ! isset($attributes['name']);

if ($this->name === null && $this->default === false) {
throw new GraphQLRuntimeException('A @Factory that has "default=false" attribute must be given a name (i.e. add a name="FooBarInput" attribute).');
throw new GraphQLRuntimeException('A #[Factory] that has "default=false" attribute must be given a name (i.e. add a name="FooBarInput" attribute).');
}
}

Expand Down
12 changes: 3 additions & 9 deletions src/Annotations/FailWith.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
use function array_key_exists;
use function is_array;

/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
* @Attributes({
* @Attribute("value", type = "mixed"),
* @Attribute("mode", type = "string")
* })
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
class FailWith implements MiddlewareAnnotationInterface
{
Expand All @@ -35,8 +27,10 @@ public function __construct(mixed $values = [], mixed $value = '__fail__with__ma
$this->value = $value;
} elseif (is_array($values) && array_key_exists('value', $values)) {
$this->value = $values['value'];
} elseif (! is_array($values)) {
$this->value = $values;
} else {
throw new BadMethodCallException('The @FailWith annotation must be passed a defaultValue. For instance: "@FailWith(null)"');
throw new BadMethodCallException('The #[FailWith] attribute must be passed a defaultValue. For instance: "#[FailWith(null)]"');
}
}

Expand Down
12 changes: 0 additions & 12 deletions src/Annotations/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@

use const E_USER_DEPRECATED;

/**
* @Annotation
* @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"),
* })
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Field extends AbstractRequest
{
Expand Down
5 changes: 1 addition & 4 deletions src/Annotations/HideIfUnauthorized.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@
use Attribute;

/**
* Fields/Queries/Mutations annotated with this annotation will be hidden from the schema if the user is not logged
* Fields/Queries/Mutations annotated with this attribute will be hidden from the schema if the user is not logged
* or has no right associated.
*
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
class HideIfUnauthorized implements MiddlewareAnnotationInterface
Expand Down
19 changes: 6 additions & 13 deletions src/Annotations/HideParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,28 @@
use function ltrim;

/**
* Use this annotation to tell GraphQLite to ignore a parameter and not expose it as an input parameter.
* Use this attribute to tell GraphQLite to ignore a parameter and not expose it as an input parameter.
* This is only possible if the parameter has a default value.
*
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Attributes({
* @Attribute("for", type = "string")
* })
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
class HideParameter implements ParameterAnnotationInterface
{
/** @var string */
private $for;
private string|null $for = null;

/** @param array<string, mixed> $values */
public function __construct(array $values = [])
public function __construct(array $values = [], string|null $for = null)
{
if (! isset($values['for'])) {
if (! isset($values['for']) && $for === null) {
return;
}

$this->for = ltrim($values['for'], '$');
$this->for = ltrim($for ?? $values['for'], '$');
}

public function getTarget(): string
{
if ($this->for === null) {
throw new BadMethodCallException('The @HideParameter annotation must be passed a target. For instance: "@HideParameter(for="$myParameterToHide")"');
throw new BadMethodCallException('The #[HideParameter] attribute must be passed a target. For instance: "#[HideParameter(for: "$myParameterToHide")]"');
}
return $this->for;
}
Expand Down
13 changes: 3 additions & 10 deletions src/Annotations/InjectUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,13 @@
use function ltrim;

/**
* Use this annotation to tell GraphQLite to inject the current logged user as an input parameter.
* Use this attribute to tell GraphQLite to inject the current logged user as an input parameter.
* If the parameter is not nullable, the user MUST be logged to access the resource.
*
* @Annotation
* @Target({"METHOD", "ANNOTATION"})
* @Attributes({
* @Attribute("for", type = "string")
* })
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
class InjectUser implements ParameterAnnotationInterface
{
/** @var string */
private $for;
private string|null $for = null;

/** @param array<string, mixed> $values */
public function __construct(array $values = [])
Expand All @@ -38,7 +31,7 @@ public function __construct(array $values = [])
public function getTarget(): string
{
if ($this->for === null) {
throw new BadMethodCallException('The @InjectUser annotation must be passed a target. For instance: "@InjectUser(for="$user")"');
throw new BadMethodCallException('The #[InjectUser] attribute must be passed a target. For instance: "#[InjectUser(for: "$user")]"');
}
return $this->for;
}
Expand Down
21 changes: 6 additions & 15 deletions src/Annotations/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,8 @@
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
* The Input attribute 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"),
* })
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Input implements TypeInterface
Expand Down Expand Up @@ -56,16 +47,16 @@ public function __construct(
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');
throw new RuntimeException('Empty class for #[Input] attribute. You MUST create the Input attribute object using the GraphQLite AnnotationReader');
}

return $this->class;
}

/** @param class-string<object> $class */
public function setClass(string $class): void
/** @param class-string<object> $className */
public function setClass(string $className): void
{
$this->class = $class;
$this->class = $className;
}

/**
Expand Down Expand Up @@ -103,7 +94,7 @@ public function isUpdate(): bool

/**
* By default there isn't support for defining the type outside
* This is used by the @Type annotation with the "external" attribute.
* This is used by the #[Type] attribute with the "external" attribute.
*/
public function isSelfType(): bool
{
Expand Down
4 changes: 0 additions & 4 deletions src/Annotations/Logged.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@

use Attribute;

/**
* @Annotation
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
class Logged implements MiddlewareAnnotationInterface
{
Expand Down
Loading

0 comments on commit 870b448

Please sign in to comment.