diff --git a/composer.json b/composer.json index 450d6f1..e86e10b 100644 --- a/composer.json +++ b/composer.json @@ -10,18 +10,18 @@ } ], "require": { - "php": "^8.1", - "doctrine/orm": "^2.18 || ^3.0", + "php": "^8.3", + "doctrine/orm": "^3.0", "doctrine/doctrine-laminas-hydrator": "^3.2", "webonyx/graphql-php": "^v15.0", - "psr/container": "^1.1 || ^2.0", + "psr/container": "^2.0", "league/event": "^3.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0 || ^12.0", + "doctrine/coding-standard": "^13.0", "doctrine/dbal": "^3.1 || ^4.0", "phpunit/phpunit": "^9.6", - "vimeo/psalm": "^5.4", + "vimeo/psalm": "^6.13", "symfony/cache": "^5.3||^6.2", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpstan/phpstan": "^1.12 || ^2.0" diff --git a/docs/driver.rst b/docs/driver.rst index 9f95085..d8b1367 100644 --- a/docs/driver.rst +++ b/docs/driver.rst @@ -15,7 +15,7 @@ Creating a Driver with all config options use ApiSkeletons\Doctrine\ORM\GraphQL\Driver; use ApiSkeletons\Doctrine\ORM\GraphQL\Filter\Filters; - $driver = new Driver($entityManager, new Config[ + $driver = new Driver($entityManager, new Config([ 'entityPrefix' => 'App\\ORM\\Entity\\', 'group' => 'customGroup', 'groupSuffix' => 'customGroupSuffix', diff --git a/src/Container.php b/src/Container.php index 49be4cb..8848527 100644 --- a/src/Container.php +++ b/src/Container.php @@ -6,6 +6,7 @@ use Closure; use GraphQL\Error\Error; +use Override; use Psr\Container\ContainerInterface; use ReflectionClass; use ReflectionException; @@ -21,12 +22,14 @@ abstract class Container implements ContainerInterface /** @var mixed[] */ protected array $register = []; + #[Override] public function has(string $id): bool { return isset($this->register[strtolower($id)]); } /** @throws Error */ + #[Override] public function get(string $id): mixed { $id = strtolower($id); diff --git a/src/Event/Criteria.php b/src/Event/Criteria.php index 08dfde4..58dec4d 100644 --- a/src/Event/Criteria.php +++ b/src/Event/Criteria.php @@ -9,6 +9,7 @@ use Doctrine\ORM\PersistentCollection; use GraphQL\Type\Definition\ResolveInfo; use League\Event\HasEventName; +use Override; /** * This event is dispatched when a Doctrine Criteria is created. @@ -34,6 +35,7 @@ public function __construct( ) { } + #[Override] public function eventName(): string { return $this->eventName; diff --git a/src/Event/EntityDefinition.php b/src/Event/EntityDefinition.php index 466b8ec..a4519e5 100644 --- a/src/Event/EntityDefinition.php +++ b/src/Event/EntityDefinition.php @@ -6,6 +6,7 @@ use ArrayObject; use League\Event\HasEventName; +use Override; /** * This event is fired each time an entity GraphQL type is created @@ -20,6 +21,7 @@ public function __construct( ) { } + #[Override] public function eventName(): string { return $this->eventName; diff --git a/src/Event/Metadata.php b/src/Event/Metadata.php index a533818..543564c 100644 --- a/src/Event/Metadata.php +++ b/src/Event/Metadata.php @@ -6,6 +6,7 @@ use ArrayObject; use League\Event\HasEventName; +use Override; /** * This event is fired when the metadta is created @@ -19,6 +20,7 @@ public function __construct( ) { } + #[Override] public function eventName(): string { return $this->eventName; diff --git a/src/Event/QueryBuilder.php b/src/Event/QueryBuilder.php index dd11961..56daeba 100644 --- a/src/Event/QueryBuilder.php +++ b/src/Event/QueryBuilder.php @@ -7,6 +7,7 @@ use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder; use GraphQL\Type\Definition\ResolveInfo; use League\Event\HasEventName; +use Override; /** * This event is fired when the QueryBuilder is created for an entity @@ -27,6 +28,7 @@ public function __construct( ) { } + #[Override] public function eventName(): string { return $this->eventName; diff --git a/src/Filter/Filters.php b/src/Filter/Filters.php index efdbf9a..38c5be9 100644 --- a/src/Filter/Filters.php +++ b/src/Filter/Filters.php @@ -17,20 +17,21 @@ */ enum Filters: string { - case EQ = 'eq'; - case NEQ = 'neq'; - case LT = 'lt'; - case LTE = 'lte'; - case GT = 'gt'; - case GTE = 'gte'; - case BETWEEN = 'between'; - case CONTAINS = 'contains'; - case STARTSWITH = 'startswith'; - case ENDSWITH = 'endswith'; - case IN = 'in'; - case NOTIN = 'notin'; - case ISNULL = 'isnull'; - case SORT = 'sort'; + case EQ = 'eq'; + case NEQ = 'neq'; + case LT = 'lt'; + case LTE = 'lte'; + case GT = 'gt'; + case GTE = 'gte'; + case BETWEEN = 'between'; + case CONTAINS = 'contains'; + case STARTSWITH = 'startswith'; + case ENDSWITH = 'endswith'; + case IN = 'in'; + case NOTIN = 'notin'; + case ISNULL = 'isnull'; + case SORT = 'sort'; + case SORTPRIORITY = 'sortPriority'; /** * Fetch the description for the filter @@ -38,20 +39,21 @@ enum Filters: string public function description(): string { return match ($this) { - self::EQ => 'Equals', - self::NEQ => 'Not equals', - self::LT => 'Less than', - self::LTE => 'Less than or equals', - self::GT => 'Greater than', - self::GTE => 'Greater than or equals', - self::BETWEEN => 'Is between from and to inclusive of from and to', - self::CONTAINS => 'Contains the value. Strings only.', - self::STARTSWITH => 'Starts with the value. Strings only.', - self::ENDSWITH => 'Ends with the value. Strings only.', - self::IN => 'In the array of values', - self::NOTIN => 'Not in the array of values', - self::ISNULL => 'Is null', - self::SORT => 'Sort by field. ASC or DESC.', + self::EQ => 'Equals', + self::NEQ => 'Not equals', + self::LT => 'Less than', + self::LTE => 'Less than or equals', + self::GT => 'Greater than', + self::GTE => 'Greater than or equals', + self::BETWEEN => 'Is between from and to inclusive of from and to', + self::CONTAINS => 'Contains the value. Strings only.', + self::STARTSWITH => 'Starts with the value. Strings only.', + self::ENDSWITH => 'Ends with the value. Strings only.', + self::IN => 'In the array of values', + self::NOTIN => 'Not in the array of values', + self::ISNULL => 'Is null', + self::SORT => 'Sort by field. ASC or DESC.', + self::SORTPRIORITY => 'Specify the sort priority of a field. Priorities are sorted lowest number first. Sort must also be speciifed.', }; } @@ -61,20 +63,21 @@ public function description(): string public function type(ScalarType|ListOfType $type): Type { return match ($this) { - self::EQ => $type, - self::NEQ => $type, - self::LT => $type, - self::LTE => $type, - self::GT => $type, - self::GTE => $type, - self::BETWEEN => new Between($type), - self::CONTAINS => $type, - self::STARTSWITH => $type, - self::ENDSWITH => $type, - self::IN => Type::listOf($type), - self::NOTIN => Type::listOf($type), - self::ISNULL => Type::boolean(), - self::SORT => Type::string(), + self::EQ => $type, + self::NEQ => $type, + self::LT => $type, + self::LTE => $type, + self::GT => $type, + self::GTE => $type, + self::BETWEEN => new Between($type), + self::CONTAINS => $type, + self::STARTSWITH => $type, + self::ENDSWITH => $type, + self::IN => Type::listOf($type), + self::NOTIN => Type::listOf($type), + self::ISNULL => Type::boolean(), + self::SORT => Type::string(), + self::SORTPRIORITY => Type::int(), }; } diff --git a/src/Filter/QueryBuilder.php b/src/Filter/QueryBuilder.php index e36a83f..ebd2add 100644 --- a/src/Filter/QueryBuilder.php +++ b/src/Filter/QueryBuilder.php @@ -6,9 +6,15 @@ use ApiSkeletons\Doctrine\ORM\GraphQL\Type\Entity\Entity; use Doctrine\ORM\QueryBuilder as DoctrineQueryBuilder; +use GraphQL\Error\Error; use function array_flip; -use function method_exists; +use function implode; +use function key; +use function print_r; +use function strcmp; +use function strtoupper; +use function uasort; use function uniqid; /** @@ -17,6 +23,9 @@ */ class QueryBuilder { + /** @var mixed[] */ + private array $sortFields = []; + /** * Add where clauses to a QueryBuilder based on the FilterType of the entity * @@ -35,13 +44,27 @@ public function apply( foreach ($filters as $filter => $value) { $filter = Filters::from($filter); - if (method_exists($this, $filter->value) === false) { - $this->default($filter, $queryBuilderField, $value, $queryBuilder); - } else { - $this->{$filter->value}($queryBuilderField, $value, $queryBuilder); + switch ($filter) { + case Filters::EQ: + case Filters::NEQ: + case Filters::GT: + case Filters::GTE: + case Filters::LT: + case Filters::LTE: + case Filters::IN: + case Filters::NOTIN: + case Filters::ISNULL: + // These filters are handled by the default method + $this->default($filter, $queryBuilderField, $value, $queryBuilder); + break; + default: + $this->{$filter->value}($queryBuilderField, $value, $queryBuilder); + break; } } } + + $this->applySort($queryBuilder); } /** @@ -49,6 +72,38 @@ public function apply( */ protected function default(Filters $filter, string $field, mixed $value, DoctrineQueryBuilder $queryBuilder): void { + /** + * psalm errors without proper filtering here + */ + switch ($filter) { + case Filters::EQ: + case Filters::NEQ: + case Filters::GT: + case Filters::GTE: + case Filters::LT: + case Filters::LTE: + case Filters::IN: + case Filters::NOTIN: + break; + case Filters::ISNULL: + if ($value) { + $queryBuilder + ->andWhere( + $queryBuilder->expr()->isNull($field), + ); + } else { + $queryBuilder + ->andWhere( + $queryBuilder->expr()->isNotNull($field), + ); + } + + return; + + default: + return; + } + $parameter = 'p' . uniqid(); $queryBuilder ->andWhere( @@ -84,7 +139,7 @@ protected function contains(string $field, string $value, DoctrineQueryBuilder $ ->setParameter($parameter, '%' . $value . '%'); } - public function startsWith(string $field, string $value, DoctrineQueryBuilder $queryBuilder): void + protected function startsWith(string $field, string $value, DoctrineQueryBuilder $queryBuilder): void { $parameter = 'p' . uniqid(); $queryBuilder @@ -94,7 +149,7 @@ public function startsWith(string $field, string $value, DoctrineQueryBuilder $q ->setParameter($parameter, $value . '%'); } - public function endsWith(string $field, string $value, DoctrineQueryBuilder $queryBuilder): void + protected function endsWith(string $field, string $value, DoctrineQueryBuilder $queryBuilder): void { $parameter = 'p' . uniqid(); $queryBuilder @@ -104,7 +159,7 @@ public function endsWith(string $field, string $value, DoctrineQueryBuilder $que ->setParameter($parameter, '%' . $value); } - public function isnull(string $field, bool $value, DoctrineQueryBuilder $queryBuilder): void + protected function isnull(string $field, bool $value, DoctrineQueryBuilder $queryBuilder): void { if ($value === true) { $queryBuilder->andWhere( @@ -119,6 +174,56 @@ public function isnull(string $field, bool $value, DoctrineQueryBuilder $queryBu protected function sort(string $field, string $direction, DoctrineQueryBuilder $queryBuilder): void { - $queryBuilder->addOrderBy($field, $direction); + if (! isset($this->sortFields[$field])) { + $this->sortFields[$field] = []; + } + + // This method is used to set the sort direction for a field + // It will be used to apply sorting later in the applySort method + $this->sortFields[$field]['direction'] = strtoupper($direction); + } + + protected function sortPriority(string $field, int $priority, DoctrineQueryBuilder $queryBuilder): void + { + if (! isset($this->sortFields[$field])) { + $this->sortFields[$field] = []; + } + + // This method is used to set the sort priority for a field + // It will be used to apply sorting later in the applySort method + $this->sortFields[$field]['priority'] = $priority; + } + + protected function applySort(DoctrineQueryBuilder $queryBuilder): void + { + // If no sort fields were added, do nothing + if (! $this->sortFields) { + return; + } + + // Sort fields by priority if set, otherwise by field name + uasort($this->sortFields, static function ($a, $b) { + if (isset($a['priority']) && isset($b['priority'])) { + return $a['priority'] <=> $b['priority']; + } + + return strcmp(key($a), key($b)); + }); + + $sortStrings = []; + + foreach ($this->sortFields as $field => $sort) { + // If the direction is not set, default to 'ASC' + if (! isset($sort['direction'])) { + throw new Error( + "Sort direction for field '" + . $field + . "' is not set but a sortPriority was. " + . "Please use the 'sort' filter to set the direction.", + ); + } + + $queryBuilder->addOrderBy($field, $sort['direction']); + } } } diff --git a/src/Hydrator/HydratorContainer.php b/src/Hydrator/HydratorContainer.php index 3cbe515..350a147 100644 --- a/src/Hydrator/HydratorContainer.php +++ b/src/Hydrator/HydratorContainer.php @@ -11,6 +11,7 @@ use GraphQL\Error\Error; use Laminas\Hydrator\NamingStrategy\MapNamingStrategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; use function assert; use function class_implements; @@ -36,6 +37,7 @@ public function __construct( } /** @throws Error */ + #[Override] public function get(string $id): mixed { if ($this->has($id)) { diff --git a/src/Hydrator/Strategy/AssociationDefault.php b/src/Hydrator/Strategy/AssociationDefault.php index bb2d49a..4b98a8e 100644 --- a/src/Hydrator/Strategy/AssociationDefault.php +++ b/src/Hydrator/Strategy/AssociationDefault.php @@ -5,6 +5,7 @@ namespace ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; /** * Take no action on an association. This class exists to @@ -13,6 +14,7 @@ class AssociationDefault extends Collection implements StrategyInterface { + #[Override] public function extract(mixed $value, object|null $object = null): mixed { return $value; @@ -23,6 +25,7 @@ public function extract(mixed $value, object|null $object = null): mixed * * @codeCoverageIgnore */ + #[Override] public function hydrate(mixed $value, array|null $data): mixed { return $value; diff --git a/src/Hydrator/Strategy/Collection.php b/src/Hydrator/Strategy/Collection.php index 7662ba8..00c3745 100644 --- a/src/Hydrator/Strategy/Collection.php +++ b/src/Hydrator/Strategy/Collection.php @@ -12,6 +12,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata; use InvalidArgumentException; use LogicException; +use Override; use ReflectionException; use function is_array; @@ -39,11 +40,13 @@ public function __construct(Inflector|null $inflector = null) $this->inflector = $inflector ?? InflectorFactory::create()->build(); } + #[Override] public function setCollectionName(string $collectionName): void { $this->collectionName = $collectionName; } + #[Override] public function getCollectionName(): string { if ($this->collectionName === null) { @@ -53,11 +56,13 @@ public function getCollectionName(): string return $this->collectionName; } + #[Override] public function setClassMetadata(ClassMetadata $classMetadata): void { $this->metadata = $classMetadata; } + #[Override] public function getClassMetadata(): ClassMetadata { if ($this->metadata === null) { @@ -67,11 +72,13 @@ public function getClassMetadata(): ClassMetadata return $this->metadata; } + #[Override] public function setObject(object $object): void { $this->object = $object; } + #[Override] public function getObject(): object { if ($this->object === null) { @@ -89,6 +96,7 @@ public function getObject(): object * * @return mixed Returns the value that should be extracted. */ + #[Override] public function extract(mixed $value, object|null $object = null): mixed { return $value; diff --git a/src/Hydrator/Strategy/FieldDefault.php b/src/Hydrator/Strategy/FieldDefault.php index 7d2eba7..3f223ee 100644 --- a/src/Hydrator/Strategy/FieldDefault.php +++ b/src/Hydrator/Strategy/FieldDefault.php @@ -5,6 +5,7 @@ namespace ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; /** * Return the same value @@ -12,6 +13,7 @@ class FieldDefault extends Collection implements StrategyInterface { + #[Override] public function extract(mixed $value, object|null $object = null): mixed { return $value; @@ -22,6 +24,7 @@ public function extract(mixed $value, object|null $object = null): mixed * * @codeCoverageIgnore */ + #[Override] public function hydrate(mixed $value, array|null $data): mixed { return $value; diff --git a/src/Hydrator/Strategy/ToBoolean.php b/src/Hydrator/Strategy/ToBoolean.php index cb8b54d..dfec0ce 100644 --- a/src/Hydrator/Strategy/ToBoolean.php +++ b/src/Hydrator/Strategy/ToBoolean.php @@ -5,6 +5,7 @@ namespace ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; /** * Transform a value into a php native boolean @@ -14,6 +15,7 @@ class ToBoolean extends Collection implements StrategyInterface { + #[Override] public function extract(mixed $value, object|null $object = null): bool|null { if ($value === null) { @@ -30,6 +32,7 @@ public function extract(mixed $value, object|null $object = null): bool|null * * @codeCoverageIgnore */ + #[Override] public function hydrate(mixed $value, array|null $data): bool|null { if ($value === null) { diff --git a/src/Hydrator/Strategy/ToFloat.php b/src/Hydrator/Strategy/ToFloat.php index 51f9953..6949fb0 100644 --- a/src/Hydrator/Strategy/ToFloat.php +++ b/src/Hydrator/Strategy/ToFloat.php @@ -5,6 +5,7 @@ namespace ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; use function floatval; @@ -16,6 +17,7 @@ class ToFloat extends Collection implements StrategyInterface { + #[Override] public function extract(mixed $value, object|null $object = null): mixed { if ($value === null) { @@ -32,6 +34,7 @@ public function extract(mixed $value, object|null $object = null): mixed * * @codeCoverageIgnore */ + #[Override] public function hydrate(mixed $value, array|null $data): mixed { if ($value === null) { diff --git a/src/Hydrator/Strategy/ToInteger.php b/src/Hydrator/Strategy/ToInteger.php index f1e2e09..3144643 100644 --- a/src/Hydrator/Strategy/ToInteger.php +++ b/src/Hydrator/Strategy/ToInteger.php @@ -5,6 +5,7 @@ namespace ApiSkeletons\Doctrine\ORM\GraphQL\Hydrator\Strategy; use Laminas\Hydrator\Strategy\StrategyInterface; +use Override; use function intval; @@ -16,6 +17,7 @@ class ToInteger extends Collection implements StrategyInterface { + #[Override] public function extract(mixed $value, object|null $object = null): mixed { if ($value === null) { @@ -32,6 +34,7 @@ public function extract(mixed $value, object|null $object = null): mixed * * @codeCoverageIgnore */ + #[Override] public function hydrate(mixed $value, array|null $data): mixed { if ($value === null) { diff --git a/src/Metadata/GlobalEnable.php b/src/Metadata/GlobalEnable.php index 63a367b..e89e9cb 100644 --- a/src/Metadata/GlobalEnable.php +++ b/src/Metadata/GlobalEnable.php @@ -11,6 +11,7 @@ use ArrayObject; use Doctrine\ORM\EntityManager; use League\Event\EventDispatcher; +use Override; use function in_array; @@ -97,6 +98,7 @@ private function buildAssociationMetadata(string $entityClass): void } } + #[Override] protected function getConfig(): Config { return $this->config; diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 461dcbb..d02cb3e 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -14,6 +14,7 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; use League\Event\EventDispatcher; +use Override; use ReflectionClass; use function assert; @@ -205,6 +206,7 @@ private function buildMetadataForAssociations( } } + #[Override] protected function getConfig(): Config { return $this->config; diff --git a/src/Type/Blob.php b/src/Type/Blob.php index 60988f0..fe0f307 100644 --- a/src/Type/Blob.php +++ b/src/Type/Blob.php @@ -8,6 +8,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function base64_decode; use function base64_encode; @@ -22,6 +23,7 @@ class Blob extends ScalarType { public string|null $description = 'A binary file base64 encoded.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): string { // @codeCoverageIgnoreStart @@ -34,6 +36,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): mixed { if (! is_string($value)) { @@ -49,6 +52,7 @@ public function parseValue(mixed $value): mixed return $data; } + #[Override] public function serialize(mixed $value): mixed { if (! $value) { diff --git a/src/Type/Date.php b/src/Type/Date.php index a4d2810..00ccf3f 100644 --- a/src/Type/Date.php +++ b/src/Type/Date.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; use function preg_match; @@ -21,6 +22,7 @@ class Date extends ScalarType public string|null $description = 'The `Date` scalar type represents datetime data.' . 'The format is e.g. 2004-02-12.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): DateTime|null { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): DateTime { if (! is_string($value)) { @@ -46,6 +49,7 @@ public function parseValue(mixed $value): DateTime return DateTime::createFromFormat(DateTime::ATOM, $value . 'T00:00:00+00:00'); } + #[Override] public function serialize(mixed $value): string|null { if (is_string($value)) { diff --git a/src/Type/DateImmutable.php b/src/Type/DateImmutable.php index eb1dcd3..d25851c 100644 --- a/src/Type/DateImmutable.php +++ b/src/Type/DateImmutable.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; use function preg_match; @@ -21,6 +22,7 @@ class DateImmutable extends ScalarType public string|null $description = 'The `date_immutable` scalar type represents datetime data.' . 'The format is e.g. 2004-02-12.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): DateTimeImmutable|false { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): DateTimeImmutable|false { if (! is_string($value)) { @@ -46,6 +49,7 @@ public function parseValue(mixed $value): DateTimeImmutable|false return DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $value . 'T00:00:00+00:00'); } + #[Override] public function serialize(mixed $value): string|null { if (is_string($value)) { diff --git a/src/Type/DateTime.php b/src/Type/DateTime.php index 21cc782..6daff48 100644 --- a/src/Type/DateTime.php +++ b/src/Type/DateTime.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; @@ -21,6 +22,7 @@ class DateTime extends ScalarType public string|null $description = 'The `datetime` scalar type represents datetime data.' . 'The format is ISO-8601 e.g. 2004-02-12T15:19:21+00:00.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): PHPDateTime { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): PHPDateTime { if (! is_string($value)) { @@ -48,6 +51,7 @@ public function parseValue(mixed $value): PHPDateTime return $data; } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTime) { diff --git a/src/Type/DateTimeImmutable.php b/src/Type/DateTimeImmutable.php index c34d9e4..aeaad76 100644 --- a/src/Type/DateTimeImmutable.php +++ b/src/Type/DateTimeImmutable.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; @@ -21,6 +22,7 @@ class DateTimeImmutable extends ScalarType public string|null $description = 'The `datetime_immutable` scalar type represents datetime data.' . 'The format is ISO-8601 e.g. 2004-02-12T15:19:21+00:00'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): PHPDateTimeImmutable { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): PHPDateTimeImmutable { if (! is_string($value)) { @@ -48,6 +51,7 @@ public function parseValue(mixed $value): PHPDateTimeImmutable return $data; } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTimeImmutable) { diff --git a/src/Type/DateTimeTZ.php b/src/Type/DateTimeTZ.php index d628168..de5ac5c 100644 --- a/src/Type/DateTimeTZ.php +++ b/src/Type/DateTimeTZ.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; @@ -21,6 +22,7 @@ class DateTimeTZ extends ScalarType public string|null $description = 'The `datetimetz` scalar type represents datetime data.' . 'The format is ISO-8601 e.g. 2004-02-12T15:19:21+00:00.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): PHPDateTimeTZ { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): PHPDateTimeTZ { if (! is_string($value)) { @@ -48,6 +51,7 @@ public function parseValue(mixed $value): PHPDateTimeTZ return $data; } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTimeTZ) { diff --git a/src/Type/DateTimeTZImmutable.php b/src/Type/DateTimeTZImmutable.php index 0858eac..0ccd638 100644 --- a/src/Type/DateTimeTZImmutable.php +++ b/src/Type/DateTimeTZImmutable.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; @@ -21,6 +22,7 @@ class DateTimeTZImmutable extends ScalarType public string|null $description = 'The `datetimetz_immutable` scalar type represents datetime data.' . 'The format is ISO-8601 e.g. 2004-02-12T15:19:21+00:00'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): PHPDateTimeTZImmutable { // @codeCoverageIgnoreStart @@ -33,6 +35,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $this->parseValue($valueNode->value); } + #[Override] public function parseValue(mixed $value): PHPDateTimeTZImmutable { if (! is_string($value)) { @@ -48,6 +51,7 @@ public function parseValue(mixed $value): PHPDateTimeTZImmutable return $data; } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTimeTZImmutable) { diff --git a/src/Type/Entity/EntityTypeContainer.php b/src/Type/Entity/EntityTypeContainer.php index 6305056..1310f6d 100644 --- a/src/Type/Entity/EntityTypeContainer.php +++ b/src/Type/Entity/EntityTypeContainer.php @@ -6,6 +6,7 @@ use ApiSkeletons\Doctrine\ORM\GraphQL\Container; use ApiSkeletons\Doctrine\ORM\GraphQL\Driver; +use Override; use function assert; use function strtolower; @@ -25,6 +26,7 @@ public function __construct( /** * Use the metadata to determine if the entity is available */ + #[Override] public function has(string $id): bool { return isset($this->container->get('metadata')[$id]); @@ -33,6 +35,7 @@ public function has(string $id): bool /** * Create and return an Entity object */ + #[Override] public function get(string $id, string|null $eventName = null): mixed { // Allow for entities with a custom eventName diff --git a/src/Type/Json.php b/src/Type/Json.php index 44b6318..32b678d 100644 --- a/src/Type/Json.php +++ b/src/Type/Json.php @@ -8,6 +8,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; use function json_decode; @@ -21,6 +22,7 @@ class Json extends ScalarType // phpcs:disable SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingAnyTypeHint public string|null $description = 'The `json` scalar type represents json data.'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): string { // @codeCoverageIgnoreStart @@ -37,6 +39,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): * * @throws Error */ + #[Override] public function parseValue(mixed $value): array|null { if (! is_string($value)) { @@ -52,7 +55,8 @@ public function parseValue(mixed $value): array|null return $data; } - public function serialize(mixed $value): string|null + #[Override] + public function serialize(mixed $value): false|string { return json_encode($value); } diff --git a/src/Type/Time.php b/src/Type/Time.php index 644c64f..62a9c8d 100644 --- a/src/Type/Time.php +++ b/src/Type/Time.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; use function preg_match; @@ -22,6 +23,7 @@ class Time extends ScalarType public string|null $description = 'The `Time` scalar type represents time data.' . 'The format is e.g. 24 hour:minutes:seconds.microseconds'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): string|null { // @codeCoverageIgnoreStart @@ -37,6 +39,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): /** * Parse H:i:s.u and H:i:s */ + #[Override] public function parseValue(mixed $value): PHPDateTime { if (! is_string($value)) { @@ -55,6 +58,7 @@ public function parseValue(mixed $value): PHPDateTime return PHPDateTime::createFromFormat('H:i:s.u', $value); } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTime) { diff --git a/src/Type/TimeImmutable.php b/src/Type/TimeImmutable.php index bf4e50d..1cc747a 100644 --- a/src/Type/TimeImmutable.php +++ b/src/Type/TimeImmutable.php @@ -9,6 +9,7 @@ use GraphQL\Language\AST\Node as ASTNode; use GraphQL\Language\AST\StringValueNode; use GraphQL\Type\Definition\ScalarType; +use Override; use function is_string; use function preg_match; @@ -22,6 +23,7 @@ class TimeImmutable extends ScalarType public string|null $description = 'The `Time` scalar type represents time data.' . 'The format is e.g. 24 hour:minutes:seconds'; + #[Override] public function parseLiteral(ASTNode $valueNode, array|null $variables = null): string { // @codeCoverageIgnoreStart @@ -34,6 +36,7 @@ public function parseLiteral(ASTNode $valueNode, array|null $variables = null): return $valueNode->value; } + #[Override] public function parseValue(mixed $value): PHPDateTime|false { if (! is_string($value)) { @@ -52,6 +55,7 @@ public function parseValue(mixed $value): PHPDateTime|false return PHPDateTime::createFromFormat('H:i:s.u', $value); } + #[Override] public function serialize(mixed $value): string|null { if ($value instanceof PHPDateTime) { diff --git a/test/Feature/Filter/ConfigExcludeFiltersTest.php b/test/Feature/Filter/ConfigExcludeFiltersTest.php index 092877e..539be2d 100644 --- a/test/Feature/Filter/ConfigExcludeFiltersTest.php +++ b/test/Feature/Filter/ConfigExcludeFiltersTest.php @@ -47,28 +47,28 @@ public function testConfigExcludeFilters(): void $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "eq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage()); + $this->assertEquals('Field "eq" is not defined by type "Filters_String_2fcc46c308f783c42451d0c9ee076e5b".', $error->getMessage()); } $query = '{ artists (filter: { name: { neq: "Grateful Dead" } } ) { edges { node { name } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "neq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage()); + $this->assertEquals('Field "neq" is not defined by type "Filters_String_2fcc46c308f783c42451d0c9ee076e5b".', $error->getMessage()); } $query = '{ artists { edges { node { performances ( filter: {venue: { neq: "test"} } ) { edges { node { venue } } } } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "neq" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0".', $error->getMessage()); + $this->assertEquals('Field "neq" is not defined by type "Filters_String_2fcc46c308f783c42451d0c9ee076e5b".', $error->getMessage()); } $query = '{ artists { edges { node { performances ( filter: {venue: { contains: "test" } } ) { edges { node { venue } } } } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "contains" is not defined by type "Filters_String_0812311810b0ba1d34247150620b78b0". Did you mean "notin"?', $error->getMessage()); + $this->assertEquals('Field "contains" is not defined by type "Filters_String_2fcc46c308f783c42451d0c9ee076e5b". Did you mean "notin"?', $error->getMessage()); } } } diff --git a/test/Feature/Filter/ExcludeFiltersTest.php b/test/Feature/Filter/ExcludeFiltersTest.php index 0e2e929..dd21176 100644 --- a/test/Feature/Filter/ExcludeFiltersTest.php +++ b/test/Feature/Filter/ExcludeFiltersTest.php @@ -39,28 +39,28 @@ public function testExcludeCriteria(): void $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "eq" is not defined by type "Filters_String_a03586330c4e7326edac556450d913ee".', $error->getMessage()); + $this->assertEquals('Field "eq" is not defined by type "Filters_String_d85f45158139644c1511e7f9d22d6068".', $error->getMessage()); } $query = '{ artists (filter: { name: { neq: "Grateful Dead" } } ) { edges { node { name } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "neq" is not defined by type "Filters_String_a03586330c4e7326edac556450d913ee".', $error->getMessage()); + $this->assertEquals('Field "neq" is not defined by type "Filters_String_d85f45158139644c1511e7f9d22d6068".', $error->getMessage()); } $query = '{ artists { edges { node { performances ( filter: {venue: { neq: "test"} } ) { edges { node { venue } } } } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "neq" is not defined by type "Filters_String_e55a7b533af3c46236f06d0fb99f08c6". Did you mean "eq"?', $error->getMessage()); + $this->assertEquals('Field "neq" is not defined by type "Filters_String_3d2660e0d014aec30b0fc5d8fef65535". Did you mean "eq"?', $error->getMessage()); } $query = '{ artists { edges { node { performances ( filter: {venue: { contains: "test" } } ) { edges { node { venue } } } } } } }'; $result = GraphQL::executeQuery($schema, $query); foreach ($result->errors as $error) { - $this->assertEquals('Field "contains" is not defined by type "Filters_String_e55a7b533af3c46236f06d0fb99f08c6". Did you mean "notin"?', $error->getMessage()); + $this->assertEquals('Field "contains" is not defined by type "Filters_String_3d2660e0d014aec30b0fc5d8fef65535". Did you mean "notin"?', $error->getMessage()); } } } diff --git a/test/Feature/Resolve/EntityFilterTest.php b/test/Feature/Resolve/EntityFilterTest.php index 34bcb4f..b37717e 100644 --- a/test/Feature/Resolve/EntityFilterTest.php +++ b/test/Feature/Resolve/EntityFilterTest.php @@ -256,7 +256,7 @@ public function testnotin(Schema $schema): void /** @dataProvider schemaProvider */ public function testsort(Schema $schema): void { - $query = '{ performance ( filter: {artist: { eq: 1 } id: { sort: "desc" } } ) { edges { node { id } } } }'; + $query = '{ performance ( filter: {artist: { eq: 1 } id: { sort: "desc" sortPriority: 1 } } ) { edges { node { id } } } }'; $result = GraphQL::executeQuery($schema, $query); $data = $result->toArray()['data']; @@ -264,7 +264,7 @@ public function testsort(Schema $schema): void $this->assertEquals(5, count($data['performance']['edges'])); $this->assertEquals(5, $data['performance']['edges'][0]['node']['id']); - $query = '{ performance ( filter: {artist: { eq: 1 } venue: { sort: "asc" } } ) { edges { node { id } } } }'; + $query = '{ performance ( filter: {artist: { eq: 1 } venue: { sort: "asc" sortPriority: 1 } } ) { edges { node { id } } } }'; $result = GraphQL::executeQuery($schema, $query); $data = $result->toArray()['data']; @@ -272,7 +272,7 @@ public function testsort(Schema $schema): void $this->assertEquals(5, count($data['performance']['edges'])); $this->assertEquals(5, $data['performance']['edges'][0]['node']['id']); - $query = '{ performance ( filter: {artist: { eq: 1 } venue: { sort: "desc" } } ) { edges { node { id } } } }'; + $query = '{ performance ( filter: {artist: { eq: 1 } venue: { sort: "desc" sortPriority: 1 } } ) { edges { node { id } } } }'; $result = GraphQL::executeQuery($schema, $query); $data = $result->toArray()['data'];