diff --git a/Command/GraphQLConfigureCommand.php b/Command/GraphQLConfigureCommand.php index 6a2dbfb..5994125 100644 --- a/Command/GraphQLConfigureCommand.php +++ b/Command/GraphQLConfigureCommand.php @@ -13,7 +13,7 @@ class GraphQLConfigureCommand extends Command { - const PROJECT_NAMESPACE = 'App'; + public const PROJECT_NAMESPACE = 'App'; /** @var Container */ protected $container; @@ -80,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } $originalConfigData = file_get_contents($configFile); - if (strpos($originalConfigData, 'graphql') === false) { + if (!str_contains($originalConfigData, 'graphql')) { $projectNameSpace = self::PROJECT_NAMESPACE; $configData = <<container->get('router')->getRouteCollection()->getResources(); foreach ($routerResources as $resource) { /** @var FileResource|DirectoryResource $resource */ - if (method_exists($resource, 'getResource') && substr($resource->getResource(), -11) == 'routes.yaml') { + if (method_exists($resource, 'getResource') && str_ends_with($resource->getResource(), 'routes.yaml')) { return $resource->getResource(); } } @@ -136,7 +136,7 @@ protected function graphQLRouteExists() $routerResources = $this->container->get('router')->getRouteCollection()->getResources(); foreach ($routerResources as $resource) { /** @var FileResource|DirectoryResource $resource */ - if (method_exists($resource, 'getResource') && strpos($resource->getResource(), 'GraphQLController.php') !== false) { + if (method_exists($resource, 'getResource') && str_contains($resource->getResource(), 'GraphQLController.php')) { return true; } } diff --git a/Config/Rule/TypeValidationRule.php b/Config/Rule/TypeValidationRule.php index c4597e7..a457ecc 100644 --- a/Config/Rule/TypeValidationRule.php +++ b/Config/Rule/TypeValidationRule.php @@ -22,7 +22,7 @@ public function validate($data, $ruleInfo) if (($ruleInfo == TypeService::TYPE_CALLABLE) && ( is_callable($data) || - (is_array($data) && count($data) == 2 && substr($data[0], 0, 1) == '@')) + (is_array($data) && count($data) == 2 && str_starts_with((string) $data[0], '@'))) ) { return true; } diff --git a/Controller/GraphQLController.php b/Controller/GraphQLController.php index 1287137..141880a 100644 --- a/Controller/GraphQLController.php +++ b/Controller/GraphQLController.php @@ -20,12 +20,10 @@ class GraphQLController extends AbstractController { protected $container; - protected $params; - public function __construct(ContainerInterface $container, ParameterBagInterface $params) + public function __construct(ContainerInterface $container, protected ParameterBagInterface $params) { $this->container = $container; - $this->params = $params; } /** @@ -35,11 +33,11 @@ public function __construct(ContainerInterface $container, ParameterBagInterface * * @return JsonResponse */ - public function defaultAction() + public function defaultAction(): JsonResponse { try { $this->initializeSchemaService(); - } catch (UnableToInitializeSchemaServiceException $e) { + } catch (UnableToInitializeSchemaServiceException) { return new JsonResponse( [['message' => 'Schema class ' . $this->getSchemaClass() . ' does not exist']], 200, @@ -47,15 +45,13 @@ public function defaultAction() ); } - if ($this->container->get('request_stack')->getCurrentRequest()->getMethod() == 'OPTIONS') { + if ($this->container->get('request_stack')->getCurrentRequest()->getMethod() === 'OPTIONS') { return $this->createEmptyResponse(); } - list($queries, $isMultiQueryRequest) = $this->getPayload(); + [$queries, $isMultiQueryRequest] = $this->getPayload(); - $queryResponses = array_map(function ($queryData) { - return $this->executeQuery($queryData['query'], $queryData['variables']); - }, $queries); + $queryResponses = array_map(fn($queryData) => $this->executeQuery($queryData['query'], $queryData['variables']), $queries); $response = new JsonResponse($isMultiQueryRequest ? $queryResponses : $queryResponses[0], 200, $this->getParam('graphql.response.headers')); @@ -66,12 +62,12 @@ public function defaultAction() return $response; } - protected function createEmptyResponse() + protected function createEmptyResponse(): JsonResponse { return new JsonResponse([], 200, $this->getResponseHeaders()); } - protected function executeQuery($query, $variables) + protected function executeQuery($query, $variables): array { /** @var Processor $processor */ $processor = $this->container->get('graphql.processor'); @@ -85,7 +81,7 @@ protected function executeQuery($query, $variables) * * @throws \Exception */ - protected function getPayload() + protected function getPayload(): array { $request = $this->container->get('request_stack')->getCurrentRequest(); $query = $request->get('query', null); @@ -103,7 +99,7 @@ protected function getPayload() 'variables' => [], ]; } else { - $params = json_decode($content, true); + $params = json_decode((string) $content, true); if ($params) { // check for a list of queries @@ -114,7 +110,7 @@ protected function getPayload() } foreach ($params as $queryParams) { - $query = isset($queryParams['query']) ? $queryParams['query'] : $query; + $query = $queryParams['query'] ?? $query; if (isset($queryParams['variables'])) { if (is_string($queryParams['variables'])) { @@ -198,7 +194,7 @@ protected function getSchemaService() { $serviceName = $this->getParam('graphql.schema_service'); - if (substr($serviceName ?: '', 0, 1) === '@') { + if (str_starts_with($serviceName ?: '', '@')) { return substr($serviceName, 1, strlen($serviceName) - 1); } diff --git a/DependencyInjection/Compiler/GraphQlCompilerPass.php b/DependencyInjection/Compiler/GraphQlCompilerPass.php index 3ac75a4..025f9ea 100644 --- a/DependencyInjection/Compiler/GraphQlCompilerPass.php +++ b/DependencyInjection/Compiler/GraphQlCompilerPass.php @@ -25,7 +25,7 @@ class GraphQlCompilerPass implements CompilerPassInterface public function process(ContainerBuilder $container) { if ($loggerAlias = $container->getParameter('graphql.logger')) { - if (strpos($loggerAlias, '@') === 0) { + if (str_starts_with($loggerAlias, '@')) { $loggerAlias = substr($loggerAlias, 1); } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 57c6917..d71069f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -16,7 +16,7 @@ class Configuration implements ConfigurationInterface /** * {@inheritdoc} */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('graphql'); $rootNode = $treeBuilder->getRootNode(); diff --git a/DependencyInjection/GraphQLExtension.php b/DependencyInjection/GraphQLExtension.php index a635fa6..ab43f6b 100644 --- a/DependencyInjection/GraphQLExtension.php +++ b/DependencyInjection/GraphQLExtension.php @@ -25,7 +25,7 @@ public function load(array $configs, ContainerBuilder $container) $this->config = $this->processConfiguration($configuration, $configs); $preparedHeaders = []; - $headers = $this->config['response']['headers'] ? $this->config['response']['headers'] : $this->getDefaultHeaders(); + $headers = $this->config['response']['headers'] ?: $this->getDefaultHeaders(); foreach ($headers as $header) { $preparedHeaders[$header['name']] = $header['value']; } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0b42819 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.3 +COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer +RUN apt update +RUN apt install git -y +RUN apt-get install -y \ + libzip-dev \ + zip \ + && docker-php-ext-install zip +RUN pecl install xdebug && docker-php-ext-enable xdebug +WORKDIR /var/www/html +ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/Event/ResolveEvent.php b/Event/ResolveEvent.php index ded147f..1028586 100644 --- a/Event/ResolveEvent.php +++ b/Event/ResolveEvent.php @@ -8,16 +8,6 @@ class ResolveEvent extends GenericEvent { - /** - * @var Field */ - private $field; - - /** @var array */ - private $astFields; - - /** @var mixed|null */ - private $resolvedValue; - /** * Constructor. * @@ -25,12 +15,9 @@ class ResolveEvent extends GenericEvent * @param array $astFields * @param mixed|null $resolvedValue */ - public function __construct(FieldInterface $field, array $astFields, $resolvedValue = null) + public function __construct(private readonly FieldInterface $field, private readonly array $astFields, private $resolvedValue = null) { - $this->field = $field; - $this->astFields = $astFields; - $this->resolvedValue = $resolvedValue; - parent::__construct('ResolveEvent', [$field, $astFields, $resolvedValue]); + parent::__construct('ResolveEvent', [$this->field, $this->astFields, $this->resolvedValue]); } /** diff --git a/Execution/Processor.php b/Execution/Processor.php index a21a0ab..078922f 100644 --- a/Execution/Processor.php +++ b/Execution/Processor.php @@ -30,19 +30,15 @@ class Processor extends BaseProcessor /** @var SecurityManagerInterface */ private $securityManager; - /** @var EventDispatcherInterface */ - private $eventDispatcher; - /** * Constructor. * * @param ExecutionContextInterface $executionContext * @param EventDispatcherInterface $eventDispatcher */ - public function __construct(ExecutionContextInterface $executionContext, EventDispatcherInterface $eventDispatcher) + public function __construct(ExecutionContextInterface $executionContext, private readonly EventDispatcherInterface $eventDispatcher) { $this->executionContext = $executionContext; - $this->eventDispatcher = $eventDispatcher; parent::__construct($executionContext->getSchema()); } @@ -105,7 +101,7 @@ protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $par if (($field instanceof AbstractField) && ($resolveFunc = $field->getConfig()->getResolveFunction())) { if ($this->isServiceReference($resolveFunc)) { - $service = substr($resolveFunc[0], 1); + $service = substr((string) $resolveFunc[0], 1); $method = $resolveFunc[1]; if (!$this->executionContext->getContainer()->has($service)) { throw new ResolveException(sprintf('Resolve service "%s" not found for field "%s"', $service, $field->getName())); @@ -153,7 +149,7 @@ private function assertClientHasFieldAccess(ResolveInfo $resolveInfo) private function isServiceReference($resolveFunc) { - return is_array($resolveFunc) && count($resolveFunc) == 2 && strpos($resolveFunc[0], '@') === 0; + return is_array($resolveFunc) && count($resolveFunc) == 2 && str_starts_with((string) $resolveFunc[0], '@'); } public function setLogger($logger = null) diff --git a/Security/Manager/DefaultSecurityManager.php b/Security/Manager/DefaultSecurityManager.php index 96d2ef0..37ad7dd 100644 --- a/Security/Manager/DefaultSecurityManager.php +++ b/Security/Manager/DefaultSecurityManager.php @@ -22,14 +22,10 @@ class DefaultSecurityManager implements SecurityManagerInterface /** @var bool */ private $rootOperationSecurityEnabled = false; - /** @var AuthorizationCheckerInterface */ - private $authorizationChecker; - - public function __construct(AuthorizationCheckerInterface $authorizationChecker, array $guardConfig = []) + public function __construct(private readonly AuthorizationCheckerInterface $authorizationChecker, array $guardConfig = []) { - $this->authorizationChecker = $authorizationChecker; - $this->fieldSecurityEnabled = isset($guardConfig['field']) ? $guardConfig['field'] : false; - $this->rootOperationSecurityEnabled = isset($guardConfig['operation']) ? $guardConfig['operation'] : false; + $this->fieldSecurityEnabled = $guardConfig['field'] ?? false; + $this->rootOperationSecurityEnabled = $guardConfig['operation'] ?? false; } /** diff --git a/Security/Manager/SecurityManagerInterface.php b/Security/Manager/SecurityManagerInterface.php index a1e7928..ab9aa61 100644 --- a/Security/Manager/SecurityManagerInterface.php +++ b/Security/Manager/SecurityManagerInterface.php @@ -13,8 +13,8 @@ interface SecurityManagerInterface { - const RESOLVE_ROOT_OPERATION_ATTRIBUTE = 'RESOLVE_ROOT_OPERATION'; - const RESOLVE_FIELD_ATTRIBUTE = 'RESOLVE_FIELD'; + public const RESOLVE_ROOT_OPERATION_ATTRIBUTE = 'RESOLVE_ROOT_OPERATION'; + public const RESOLVE_FIELD_ATTRIBUTE = 'RESOLVE_FIELD'; /** * @param $attribute string diff --git a/Security/Voter/AbstractListVoter.php b/Security/Voter/AbstractListVoter.php index 611801f..475d2d3 100644 --- a/Security/Voter/AbstractListVoter.php +++ b/Security/Voter/AbstractListVoter.php @@ -21,7 +21,7 @@ abstract class AbstractListVoter extends Voter /** @var bool */ private $enabled = false; - protected function supports($attribute, $subject) + protected function supports($attribute, $subject): bool { return $this->enabled && $attribute == SecurityManagerInterface::RESOLVE_ROOT_OPERATION_ATTRIBUTE; } diff --git a/Security/Voter/BlacklistVoter.php b/Security/Voter/BlacklistVoter.php index 379719d..2e04f80 100644 --- a/Security/Voter/BlacklistVoter.php +++ b/Security/Voter/BlacklistVoter.php @@ -23,7 +23,7 @@ class BlacklistVoter extends AbstractListVoter * * @return bool */ - protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { /** @var $subject Query */ return $this->isLoggedInUser($token) || !$this->inList($subject->getName()); diff --git a/Tests/DependencyInjection/GraphQLExtensionTest.php b/Tests/DependencyInjection/GraphQLExtensionTest.php index debee25..43e305e 100644 --- a/Tests/DependencyInjection/GraphQLExtensionTest.php +++ b/Tests/DependencyInjection/GraphQLExtensionTest.php @@ -66,7 +66,7 @@ public function testDefaultCanBeOverridden() ); } - private function loadContainerFromFile($file, $type, array $services = array(), $skipEnvVars = false) + private function loadContainerFromFile($file, $type, array $services = [], $skipEnvVars = false) { $container = new ContainerBuilder(); if ($skipEnvVars && !method_exists($container, 'resolveEnvPlaceholders')) { @@ -80,25 +80,16 @@ private function loadContainerFromFile($file, $type, array $services = array(), $container->registerExtension(new GraphQLExtension()); $locator = new FileLocator(__DIR__ . '/Fixtures/config/' . $type); - switch ($type) { - case 'xml': - $loader = new XmlFileLoader($container, $locator); - break; - case 'yml': - $loader = new YamlFileLoader($container, $locator); - break; - case 'php': - $loader = new PhpFileLoader($container, $locator); - break; - default: - throw new \InvalidArgumentException('Invalid file type'); - } + $loader = match ($type) { + 'xml' => new XmlFileLoader($container, $locator), + 'yml' => new YamlFileLoader($container, $locator), + 'php' => new PhpFileLoader($container, $locator), + default => throw new \InvalidArgumentException('Invalid file type'), + }; $loader->load($file . '.' . $type); - $container->getCompilerPassConfig()->setOptimizationPasses(array( - new ResolveChildDefinitionsPass(), - )); - $container->getCompilerPassConfig()->setRemovingPasses(array()); + $container->getCompilerPassConfig()->setOptimizationPasses([new ResolveChildDefinitionsPass()]); + $container->getCompilerPassConfig()->setRemovingPasses([]); $container->compile(); return $container; } diff --git a/composer.json b/composer.json index 0834bd0..c16a83f 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,22 @@ "Youshido\\GraphQLBundle\\": "" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/msklenica/GraphQL-php.git" + } + ], "require": { - "99designs/graphql": "~1", - "symfony/security-core": "~4.4 || ~5.4", - "symfony/framework-bundle": "~4.4 || ~5.4", - "php": ">=5.6" + "php": "^8.3", + "99designs/graphql": "v1.0.2", + "symfony/security-core": "~6.4", + "symfony/framework-bundle": "~6.4", + "symfony/console": "~6.4" }, "require-dev": { "phpunit/phpunit": "~9.6", - "composer/composer": "~1.2", - "symfony/yaml": "~4.4 || ~5.4" + "rector/rector": "*", + "symfony/yaml": "~6.4" } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bf353c9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + app: + hostname: app + container_name: app + build: . + volumes: + - .:/var/www/html:rw diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..08e1bb3 --- /dev/null +++ b/rector.php @@ -0,0 +1,32 @@ +withPaths([ + __DIR__ . '/Command', + __DIR__ . '/Config', + __DIR__ . '/Controller', + __DIR__ . '/DependencyInjection', + __DIR__ . '/Event', + __DIR__ . '/Exception', + __DIR__ . '/Execution', + __DIR__ . '/Field', + __DIR__ . '/Resources', + __DIR__ . '/Security', + __DIR__ . '/Tests', + ]) + ->withPhpSets(php83: true) + ->withSets([ + PHPUnitSetList::PHPUNIT_50, + PHPUnitSetList::PHPUNIT_60, + PHPUnitSetList::PHPUNIT_70, + PHPUnitSetList::PHPUNIT_80, + PHPUnitSetList::PHPUNIT_90, + ]) ->withSkip([ + \Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector::class, + \Rector\Php83\Rector\ClassConst\AddTypeToConstRector::class, + \Rector\Php82\Rector\Class_\ReadOnlyClassRector::class, + ]);