Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
7 changes: 3 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
matrix:
install-args: ['', '--prefer-lowest']
php-version: ['7.2', '7.3', '7.4', '8.0']
fail-fast: false
steps:
# Cancel previous runs of the same branch
- name: cancel
Expand All @@ -36,7 +37,7 @@ jobs:
- name: composer-cache
uses: actions/cache@v2.1.6
with:
path: ${{ needs.prepare.outputs.composercachedir }}
path: ${{ steps.composercache.outputs.dir }}
key: composer-${{ hashFiles('**/composer.json') }}-${{ matrix.install-args }}
restore-keys: |
composer-${{ hashFiles('**/composer.json') }}-${{ matrix.install-args }}
Expand All @@ -45,12 +46,11 @@ jobs:

- name: composer
run: |
composer check-platform-reqs --no-interaction
composer update ${{ matrix.install-args }} --no-interaction --no-progress --prefer-dist

- name: phpunit
run: |
vendor/bin/phpunit
vendor/bin/simple-phpunit --no-coverage

- name: phpstan-cache
uses: actions/cache@v2.1.6
Expand All @@ -66,5 +66,4 @@ jobs:
- name: phpstan
run: |
mkdir -p .phpstan-cache
APP_ENV=dev bin/console cache:clear
vendor/bin/phpstan analyse --no-progress --no-interaction --memory-limit=1G
1 change: 1 addition & 0 deletions Controller/GraphQL/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public function login(string $userName, string $password, Request $request): Use

// Fire the login event manually
$event = new InteractiveLoginEvent($request, $token);
// @phpstan-ignore-next-line BC for Symfony4
$this->eventDispatcher->dispatch($event, 'security.interactive_login');

return $user;
Expand Down
10 changes: 4 additions & 6 deletions Controller/GraphqliteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,16 @@ private function handlePsr7Request(ServerRequestInterface $request, Request $sym
return new JsonResponse($result->toArray($this->debug), $httpCodeDecider->decideHttpStatusCode($result));
}
if (is_array($result)) {
$finalResult = array_map(function (ExecutionResult $executionResult) {
$finalResult = array_map(function (ExecutionResult $executionResult): array {
return $executionResult->toArray($this->debug);
}, $result);
// Let's return the highest result.
$statuses = array_map([$httpCodeDecider, 'decideHttpStatusCode'], $result);
$status = empty($statuses) ? 500 : max($statuses);

return new JsonResponse($finalResult, $status);
}
if ($result instanceof Promise) {
throw new RuntimeException('Only SyncPromiseAdapter is supported');
}
/* @phpstan-ignore-next-line */
throw new RuntimeException('Unexpected response from StandardServer::executePsrRequest'); // @codeCoverageIgnore

throw new RuntimeException('Only SyncPromiseAdapter is supported');
}
}
34 changes: 25 additions & 9 deletions DependencyInjection/GraphqliteCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Symfony\Component\Cache\Psr16Cache;
use TheCodingMachine\GraphQLite\Mappers\StaticClassListTypeMapper;
use TheCodingMachine\GraphQLite\Mappers\StaticClassListTypeMapperFactory;
use Webmozart\Assert\Assert;
use function class_exists;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\AnnotationReader as DoctrineAnnotationReader;
Expand Down Expand Up @@ -102,14 +103,19 @@ class GraphqliteCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container): void
{
$reader = $this->getAnnotationReader();
$this->cacheDir = $container->getParameter('kernel.cache_dir');
$cacheDir = $container->getParameter('kernel.cache_dir');
Assert::string($cacheDir);
$this->cacheDir = $cacheDir;
//$inputTypeUtils = new InputTypeUtils($reader, $namingStrategy);

// Let's scan the whole container and tag the services that belong to the namespace we want to inspect.
$controllersNamespaces = $container->getParameter('graphqlite.namespace.controllers');
Assert::isIterable($controllersNamespaces);
$typesNamespaces = $container->getParameter('graphqlite.namespace.types');
Assert::isIterable($typesNamespaces);

$firewallName = $container->getParameter('graphqlite.security.firewall_name');
Assert::string($firewallName);
$firewallConfigServiceName = 'security.firewall.map.config.'.$firewallName;

// 2 seconds of TTL in environment mode. Otherwise, let's cache forever!
Expand Down Expand Up @@ -183,14 +189,23 @@ public function process(ContainerBuilder $container): void
if ($container->getParameter('graphqlite.security.introspection') === false) {
$rulesDefinition[] = $container->findDefinition(DisableIntrospection::class);
}
if ($container->getParameter('graphqlite.security.maximum_query_complexity')) {
$complexity = (int) $container->getParameter('graphqlite.security.maximum_query_complexity');
$rulesDefinition[] = $container->findDefinition(QueryComplexity::class)->setArgument(0, $complexity);

$complexity = $container->getParameter('graphqlite.security.maximum_query_complexity');
if ($complexity) {
Assert::integerish($complexity);

$rulesDefinition[] = $container->findDefinition(QueryComplexity::class)
->setArgument(0, (int) $complexity);
}
if ($container->getParameter('graphqlite.security.maximum_query_depth')) {
$depth = (int) $container->getParameter('graphqlite.security.maximum_query_depth');
$rulesDefinition[] = $container->findDefinition(QueryDepth::class)->setArgument(0, $depth);

$depth = $container->getParameter('graphqlite.security.maximum_query_depth');
if ($depth) {
Assert::integerish($depth);

$rulesDefinition[] = $container->findDefinition(QueryDepth::class)
->setArgument(0, (int) $depth);
}

$serverConfigDefinition->addMethodCall('setValidationRules', [$rulesDefinition]);

if ($disableMe === false) {
Expand Down Expand Up @@ -333,7 +348,7 @@ private function mapAdderToTag(string $tag, string $methodName, ContainerBuilder
*/
private function makePublicInjectedServices(ReflectionClass $refClass, AnnotationReader $reader, ContainerBuilder $container, bool $isController): void
{
$services = $this->getCodeCache()->get($refClass, function() use ($refClass, $reader, $container, $isController) {
$services = $this->getCodeCache()->get($refClass, function() use ($refClass, $reader, $container, $isController): array {
$services = [];
foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
$field = $reader->getRequestAnnotation($method, Field::class) ?? $reader->getRequestAnnotation($method, Query::class) ?? $reader->getRequestAnnotation($method, Mutation::class);
Expand All @@ -347,6 +362,7 @@ private function makePublicInjectedServices(ReflectionClass $refClass, Annotatio
}
}
}

return $services;
});

Expand Down Expand Up @@ -485,7 +501,7 @@ private function getClassList(string $namespace, int $globTtl = 2, bool $recursi
// The autoloader might trigger errors if the file does not respect PSR-4 or if the
// Symfony DebugAutoLoader is installed. (see https://github.com/thecodingmachine/graphqlite/issues/216)
require_once $phpFile;
// Does it exists now?
// @phpstan-ignore-next-line Does it exist now?
if (! class_exists($className, false)) {
continue;
}
Expand Down
28 changes: 12 additions & 16 deletions DependencyInjection/GraphqliteExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ public function load(array $configs, ContainerBuilder $container): void
if (!is_array($controllers)) {
$controllers = [ $controllers ];
}
$namespaceController = array_map(function($namespace) { return rtrim($namespace, '\\') . '\\'; }, $controllers);
$namespaceController = array_map(
function($namespace): string {
return rtrim($namespace, '\\') . '\\';
},
$controllers
);
} else {
$namespaceController = [];
}
Expand All @@ -51,7 +56,12 @@ public function load(array $configs, ContainerBuilder $container): void
if (!is_array($types)) {
$types = [ $types ];
}
$namespaceType = array_map(function($namespace) { return rtrim($namespace, '\\') . '\\'; }, $types);
$namespaceType = array_map(
function($namespace): string {
return rtrim($namespace, '\\') . '\\';
},
$types
);
} else {
$namespaceType = [];
}
Expand Down Expand Up @@ -84,20 +94,6 @@ public function load(array $configs, ContainerBuilder $container): void
->addTag('graphql.root_type_mapper_factory');
}

private function getNamespaceDir(string $namespace): string
{
$classNameMapper = ClassNameMapper::createFromComposerFile(null, null, true);

$possibleFileNames = $classNameMapper->getPossibleFileNames($namespace.'Xxx');
if (count($possibleFileNames) > 1) {
throw new \RuntimeException(sprintf('According to your composer.json, classes belonging to the "%s" namespace can be located in several directories: %s. This is an issue for the GraphQLite lib. Please make sure that a namespace can only be resolved to one PHP file.', $namespace, implode(", ", $possibleFileNames)));
} elseif (empty($possibleFileNames)) {
throw new \RuntimeException(sprintf('Files in namespace "%s" cannot be autoloaded by Composer. Please set up a PSR-4 autoloader in Composer or change the namespace configured in "graphqlite.namespace.controllers" and "graphqlite.namespace.types"', $namespace));
}

return substr($possibleFileNames[0], 0, -8);
}

/**
* @param array<string, int> $debug
* @return int
Expand Down
2 changes: 0 additions & 2 deletions DependencyInjection/OverblogGraphiQLEndpointWiringPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace TheCodingMachine\Graphqlite\Bundle\DependencyInjection;

use Overblog\GraphiQLBundle\Config\GraphiQLControllerEndpoint;
use Overblog\GraphiQLBundle\Config\GraphqlEndpoint\RootResolver;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
Expand Down
6 changes: 6 additions & 0 deletions GraphiQL/EndpointResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
namespace TheCodingMachine\Graphqlite\Bundle\GraphiQL;

use Overblog\GraphiQLBundle\Config\GraphiQLControllerEndpoint;
use Overblog\GraphiQLBundle\Config\GraphQLEndpoint\GraphQLEndpointInvalidSchemaException;
use Symfony\Component\HttpFoundation\RequestStack;
use Webmozart\Assert\Assert;

final class EndpointResolver implements GraphiQLControllerEndpoint
{
/**
* @var RequestStack
*/
protected $requestStack;

public function __construct(RequestStack $requestStack)
Expand All @@ -18,6 +23,7 @@ public function getBySchema($name)
{
if ('default' === $name) {
$request = $this->requestStack->getCurrentRequest();
Assert::notNull($request);

return $request->getBaseUrl().'/graphql';
}
Expand Down
5 changes: 3 additions & 2 deletions Mappers/RequestParameterMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Type;
use ReflectionNamedType;
use ReflectionParameter;
use Symfony\Component\HttpFoundation\Request;
use TheCodingMachine\GraphQLite\Annotations\ParameterAnnotations;
Expand All @@ -15,13 +16,13 @@

class RequestParameterMiddleware implements ParameterMiddlewareInterface
{

public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface
{
$parameterType = $parameter->getType();
if ($parameterType && $parameterType->getName() === Request::class) {
if ($parameterType instanceof ReflectionNamedType && $parameterType->getName() === Request::class) {
return new RequestParameter();
}

return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);
}
}
13 changes: 12 additions & 1 deletion Server/ServerConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
namespace TheCodingMachine\Graphqlite\Bundle\Server;

use GraphQL\Error\InvariantViolation;
use GraphQL\GraphQL;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Server\OperationParams;
use GraphQL\Utils\Utils;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\ValidationRule;
Expand All @@ -27,7 +30,15 @@ class ServerConfig extends \GraphQL\Server\ServerConfig
*/
public function setValidationRules($validationRules)
{
parent::setValidationRules(array_merge(DocumentValidator::defaultRules(), $validationRules));
parent::setValidationRules(
function (OperationParams $params, DocumentNode $doc, string $operationType) use ($validationRules): array {
$validationRules = is_callable($validationRules)
? $validationRules($params, $doc, $operationType)
: $validationRules;

return array_merge(DocumentValidator::defaultRules(), $validationRules);
}
);

return $this;
}
Expand Down
3 changes: 3 additions & 0 deletions Tests/FunctionalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@ private function logIn(ContainerInterface $container)
$container->get('security.token_storage')->setToken($token);
}

/**
* @requires PHP 8.0
*/
public function testPhp8Attributes(): void
{
$kernel = new GraphqliteTestingKernel();
Expand Down
1 change: 1 addition & 0 deletions Tests/NoSecurityBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function testServiceWiring(): void
$kernel = new GraphqliteTestingKernel(true, null, false, null, true, null, null, ['TheCodingMachine\\Graphqlite\\Bundle\\Tests\\NoSecurityBundleFixtures\\Controller\\']);
$kernel->boot();
$container = $kernel->getContainer();
self::assertNotNull($container);

$schema = $container->get(Schema::class);
$this->assertInstanceOf(Schema::class, $schema);
Expand Down
30 changes: 27 additions & 3 deletions Types/SymfonyUserInterfaceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,36 @@

namespace TheCodingMachine\Graphqlite\Bundle\Types;

use Symfony\Component\Security\Core\Role\Role;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\SourceField;
use TheCodingMachine\GraphQLite\Annotations\Type;
use Symfony\Component\Security\Core\User\UserInterface;
use TheCodingMachine\GraphQLite\FieldNotFoundException;

/**
* @Type(class=UserInterface::class)
* @SourceField(name="userName")
*/
class SymfonyUserInterfaceType
{
/**
* @Field
*/
public function getUserName(UserInterface $user): string
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we expecting to rename this to getUserIdentifier in a future (breaking) release?

{
// @phpstan-ignore-next-line Forward Compatibility for Symfony >=5.3
if (method_exists($user, 'getUserIdentifier')) {
return $user->getUserIdentifier();
}

// @phpstan-ignore-next-line Backward Compatibility for Symfony <5.3
if (method_exists($user, 'getUsername')) {
return $user->getUsername();
}

throw FieldNotFoundException::missingField(UserInterface::class, 'userName');
}

/**
* @Field()
* @return string[]
Expand All @@ -22,8 +41,13 @@ public function getRoles(UserInterface $user): array
{
$roles = [];
foreach ($user->getRoles() as $role) {
$roles[] = (string) $role;
// @phpstan-ignore-next-line BC for Symfony 4
if (class_exists(Role::class) && $role instanceof Role) {
$role = $role->getRole();
}

$roles[] = $role;
}
return $roles;
}
}
}
Loading