Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
language: php
php:
- 7.1
- 7.2
- 7.3
cache:
directories:
- $HOME/.composer/cache
env:
matrix:
- PREFER_LOWEST="--prefer-lowest"
- PREFER_LOWEST=""
matrix:
include:
- php: 7.3
env: PREFER_LOWEST=""
- php: 7.2
env: PREFER_LOWEST=""
- php: 7.1
env: PREFER_LOWEST=""
- php: 7.1
env: PREFER_LOWEST="--prefer-lowest"

before_script:
- composer update --prefer-dist $PREFER_LOWEST
script:
Expand Down
19 changes: 15 additions & 4 deletions src/AnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use function substr;
use TheCodingMachine\GraphQL\Controllers\Annotations\AbstractRequest;
use TheCodingMachine\GraphQL\Controllers\Annotations\Exceptions\ClassNotFoundException;
use TheCodingMachine\GraphQL\Controllers\Annotations\ExtendType;
use TheCodingMachine\GraphQL\Controllers\Annotations\Factory;
use TheCodingMachine\GraphQL\Controllers\Annotations\FailWith;
use TheCodingMachine\GraphQL\Controllers\Annotations\Logged;
Expand Down Expand Up @@ -64,14 +65,24 @@ public function __construct(Reader $reader, string $mode = self::STRICT_MODE, ar

public function getTypeAnnotation(ReflectionClass $refClass): ?Type
{
// TODO: customize the way errors are handled here!
try {
/** @var Type|null $typeField */
$typeField = $this->getClassAnnotation($refClass, Type::class);
/** @var Type|null $type */
$type = $this->getClassAnnotation($refClass, Type::class);
} catch (ClassNotFoundException $e) {
throw ClassNotFoundException::wrapException($e, $refClass->getName());
}
return $typeField;
return $type;
}

public function getExtendTypeAnnotation(ReflectionClass $refClass): ?ExtendType
{
try {
/** @var ExtendType|null $extendType */
$extendType = $this->getClassAnnotation($refClass, ExtendType::class);
} catch (ClassNotFoundException $e) {
throw ClassNotFoundException::wrapException($e, $refClass->getName());
}
return $extendType;
}

public function getRequestAnnotation(ReflectionMethod $refMethod, string $annotationName): ?AbstractRequest
Expand Down
51 changes: 51 additions & 0 deletions src/Annotations/ExtendType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Annotations;

use BadMethodCallException;
use function class_exists;
use TheCodingMachine\GraphQL\Controllers\Annotations\Exceptions\ClassNotFoundException;
use TheCodingMachine\GraphQL\Controllers\MissingAnnotationException;

/**
* 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"),
* })
*/
class ExtendType
{
/**
* @var string
*/
private $className;

/**
* @param mixed[] $attributes
*/
public function __construct(array $attributes = [])
{
if (!isset($attributes['class'])) {
throw new BadMethodCallException('In annotation @ExtendType, missing compulsory parameter "class".');
}
$this->className = $attributes['class'];
if (!class_exists($this->className)) {
throw ClassNotFoundException::couldNotFindClass($this->className);
}
}

/**
* Returns the name of the GraphQL query/mutation/field.
* If not specified, the name of the method should be used instead.
*
* @return string
*/
public function getClass(): string
{
return ltrim($this->className, '\\');
}
}
19 changes: 19 additions & 0 deletions src/Cache/CacheValidatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Cache;

/**
* An interface in charge of checking if some item coming out of cache is still valid.
*
* Useful for items depending on some file timestamp for instance.
*/
interface CacheValidatorInterface
{
/**
* Returns true if this item is valid (coming out of cache), false otherwise.
*
* @return bool
*/
public function isValid(): bool;
}
45 changes: 45 additions & 0 deletions src/Cache/FileModificationTimeCacheValidatorTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Cache;

use function filemtime;

/**
* A trait that validates a cache item based on a file modification time
*/
trait FileModificationTimeCacheValidatorTrait
{
/**
* An array mapping the file name to its latest modification date.
*
* @var array<string, int>
*/
protected $trackedFiles = [];

/**
* Adds a file to track.
* All files must not have changed for the cache to be valid.
*
* @param string $fileName
*/
public function addTrackedFile(string $fileName): void
{
$this->trackedFiles[$fileName] = filemtime($fileName);
}

/**
* Returns true if this item is valid (coming out of cache), false otherwise.
*
* @return bool
*/
public function isValid(): bool
{
foreach ($this->trackedFiles as $fileName => $mtime) {
if (filemtime($fileName) !== $mtime) {
return false;
}
}
return true;
}
}
13 changes: 13 additions & 0 deletions src/Cache/InvalidCacheItemException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Cache;


class InvalidCacheItemException extends \Exception
{
public static function create(string $name): self
{
return new self(sprintf('Cache item "%s" does not implement the SelfValidatingCacheInterface.', $name));
}
}
52 changes: 52 additions & 0 deletions src/Cache/SelfValidatingCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Cache;


use Psr\SimpleCache\CacheInterface;

class SelfValidatingCache implements SelfValidatingCacheInterface
{

/**
* @var CacheInterface
*/
private $cache;

public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}

/**
* Fetches a value from the cache.
*
* @param string $key The unique key of this item in the cache.
*
* @return mixed The value of the item from the cache, or null in case of cache miss.
*/
public function get(string $key)
{
$item = $this->cache->get($key);
if (!$item instanceof CacheValidatorInterface) {
throw InvalidCacheItemException::create($key);
}
return $item;
}

/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param string $key The key of the item to store.
* @param mixed $value The value of the item to store, must be serializable.
*
*/
public function set(string $key, $value)
{
if (!$value instanceof CacheValidatorInterface) {
throw InvalidCacheItemException::create($key);
}
$this->cache->set($key, $value);
}
}
28 changes: 28 additions & 0 deletions src/Cache/SelfValidatingCacheInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php


namespace TheCodingMachine\GraphQL\Controllers\Cache;

/**
* A cache service that can itself invalidate cache items.
*/
interface SelfValidatingCacheInterface
{
/**
* Fetches a value from the cache.
*
* @param string $key The unique key of this item in the cache.
*
* @return mixed The value of the item from the cache, or null in case of cache miss.
*/
public function get(string $key);

/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param string $key The key of the item to store.
* @param mixed $value The value of the item to store, must be serializable.
*
*/
public function set(string $key, $value);
}
11 changes: 11 additions & 0 deletions src/Mappers/CannotMapTypeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace TheCodingMachine\GraphQL\Controllers\Mappers;


use GraphQL\Type\Definition\ObjectType;
use ReflectionMethod;

class CannotMapTypeException extends \Exception implements CannotMapTypeExceptionInterface
Expand Down Expand Up @@ -48,4 +49,14 @@ public static function mustBeOutputType($subTypeName): self
{
return new self('type "'.$subTypeName.'" must be an output type.');
}

public static function createForExtendType(string $className, ObjectType $type): self
{
return new self('cannot extend GraphQL type "'.$type->name.'" mapped by class "'.$className.'". Check your TypeMapper configuration.');
}

public static function createForExtendName(string $name, ObjectType $type): self
{
return new self('cannot extend GraphQL type "'.$type->name.'" with type "'.$name.'". Check your TypeMapper configuration.');
}
}
73 changes: 71 additions & 2 deletions src/Mappers/CompositeTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use GraphQL\Type\Definition\Type;
use function is_array;
use function iterator_to_array;
use TheCodingMachine\GraphQL\Controllers\Types\MutableObjectType;

class CompositeTypeMapper implements TypeMapperInterface
{
Expand Down Expand Up @@ -59,10 +60,10 @@ public function canMapClassToType(string $className): bool
* @param string $className
* @param OutputType|null $subType
* @param RecursiveTypeMapperInterface $recursiveTypeMapper
* @return ObjectType
* @return MutableObjectType
* @throws CannotMapTypeExceptionInterface
*/
public function mapClassToType(string $className, ?OutputType $subType, RecursiveTypeMapperInterface $recursiveTypeMapper): ObjectType
public function mapClassToType(string $className, ?OutputType $subType, RecursiveTypeMapperInterface $recursiveTypeMapper): MutableObjectType
{
foreach ($this->typeMappers as $typeMapper) {
if ($typeMapper->canMapClassToType($className)) {
Expand Down Expand Up @@ -152,4 +153,72 @@ public function canMapNameToType(string $typeName): bool
}
return false;
}

/**
* Returns true if this type mapper can extend an existing type for the $className FQCN
*
* @param string $className
* @param MutableObjectType $type
* @return bool
*/
public function canExtendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
{
foreach ($this->typeMappers as $typeMapper) {
if ($typeMapper->canExtendTypeForClass($className, $type, $recursiveTypeMapper)) {
return true;
}
}
return false;
}

/**
* Extends the existing GraphQL type that is mapped to $className.
*
* @param string $className
* @param MutableObjectType $type
* @param RecursiveTypeMapperInterface $recursiveTypeMapper
* @throws CannotMapTypeExceptionInterface
*/
public function extendTypeForClass(string $className, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
{
foreach ($this->typeMappers as $typeMapper) {
if ($typeMapper->canExtendTypeForClass($className, $type, $recursiveTypeMapper)) {
$typeMapper->extendTypeForClass($className, $type, $recursiveTypeMapper);
}
}
}

/**
* Returns true if this type mapper can extend an existing type for the $typeName GraphQL type
*
* @param string $typeName
* @param MutableObjectType $type
* @return bool
*/
public function canExtendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): bool
{
foreach ($this->typeMappers as $typeMapper) {
if ($typeMapper->canExtendTypeForName($typeName, $type, $recursiveTypeMapper)) {
return true;
}
}
return false;
}

/**
* Extends the existing GraphQL type that is mapped to the $typeName GraphQL type.
*
* @param string $typeName
* @param MutableObjectType $type
* @param RecursiveTypeMapperInterface $recursiveTypeMapper
* @throws CannotMapTypeExceptionInterface
*/
public function extendTypeForName(string $typeName, MutableObjectType $type, RecursiveTypeMapperInterface $recursiveTypeMapper): void
{
foreach ($this->typeMappers as $typeMapper) {
if ($typeMapper->canExtendTypeForName($typeName, $type, $recursiveTypeMapper)) {
$typeMapper->extendTypeForName($typeName, $type, $recursiveTypeMapper);
}
}
}
}
Loading