diff --git a/Command/GraphQLConfigureCommand.php b/Command/GraphQLConfigureCommand.php index 6a2dbfb..ae90214 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; @@ -39,7 +39,7 @@ protected function configure() /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $isComposerCall = $input->getOption('composer'); @@ -59,11 +59,13 @@ protected function execute(InputInterface $input, OutputInterface $output) } else { $question = new ConfirmationQuestion(sprintf('Confirm creating class at %s ? [Y/n]', $schemaNamespace . '\\' . $className), true); if (!$inputHelper->ask($input, $output, $question)) { - return; + return Command::SUCCESS; } if (!is_dir($graphqlPath)) { - mkdir($graphqlPath, 0777, true); + if (!mkdir($graphqlPath, 0777, true) && !is_dir($graphqlPath)) { + throw new \RuntimeException(sprintf('Directory "%s" was not created', $graphqlPath)); + } } file_put_contents($classPath, $this->getSchemaClassTemplate($schemaNamespace, $className)); @@ -73,14 +75,14 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!file_exists($configFile)) { $question = new ConfirmationQuestion(sprintf('Config file not found (look at %s). Create it? [Y/n]', $configFile), true); if (!$inputHelper->ask($input, $output, $question)) { - return; + return Command::SUCCESS; } touch($configFile); } $originalConfigData = file_get_contents($configFile); - if (strpos($originalConfigData, 'graphql') === false) { + if (!str_contains($originalConfigData, 'graphql')) { $projectNameSpace = self::PROJECT_NAMESPACE; $configData = <<writeln('GraphQL default route was found.'); } } + + return Command::SUCCESS; } /** @@ -119,7 +123,7 @@ protected function getMainRouteConfig() $routerResources = $this->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 +140,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..f22a311 100644 --- a/Controller/GraphQLController.php +++ b/Controller/GraphQLController.php @@ -8,38 +8,28 @@ namespace Youshido\GraphQLBundle\Controller; -use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\Routing\Annotation\Route; use Youshido\GraphQLBundle\Exception\UnableToInitializeSchemaServiceException; use Youshido\GraphQLBundle\Execution\Processor; class GraphQLController extends AbstractController { - protected $container; - protected $params; - - public function __construct(ContainerInterface $container, ParameterBagInterface $params) + public function __construct(protected ParameterBagInterface $params) { - $this->container = $container; - $this->params = $params; } /** - * @Route("/graphql") - * * @throws \Exception * * @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 +37,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 +54,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 +73,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); @@ -93,17 +81,20 @@ protected function getPayload() $isMultiQueryRequest = false; $queries = []; - $variables = is_string($variables) ? json_decode($variables, true) ?: [] : []; + if (is_string($variables)) { + $decoded = json_decode($variables, true); + $variables = $decoded ?: []; + } $content = $request->getContent(); if (!empty($content)) { - if ($request->headers->has('Content-Type') && 'application/graphql' == $request->headers->get('Content-Type')) { + if ($request->headers->has('Content-Type') && 'application/graphql' === $request->headers->get('Content-Type')) { $queries[] = [ 'query' => $content, 'variables' => [], ]; } else { - $params = json_decode($content, true); + $params = json_decode((string) $content, true); if ($params) { // check for a list of queries @@ -114,11 +105,12 @@ 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'])) { - $variables = json_decode($queryParams['variables'], true) ?: $variables; + $decoded = json_decode($queryParams['variables'], true); + $variables = $decoded ?: $variables; } else { $variables = $queryParams['variables']; } @@ -175,12 +167,7 @@ protected function makeSchemaService() return $this->container->get($schemaClass); } - $schema = new $schemaClass(); - if ($schema instanceof ContainerAwareInterface) { - $schema->setContainer($this->container); - } - - return $schema; + return new $schemaClass(); } /** @@ -198,8 +185,8 @@ protected function getSchemaService() { $serviceName = $this->getParam('graphql.schema_service'); - if (substr($serviceName ?: '', 0, 1) === '@') { - return substr($serviceName, 1, strlen($serviceName) - 1); + if (str_starts_with($serviceName ?: '', '@')) { + return substr($serviceName, 1); } return $serviceName; diff --git a/DependencyInjection/Compiler/GraphQLEventListenerPass.php b/DependencyInjection/Compiler/GraphQLEventListenerPass.php new file mode 100644 index 0000000..40271b1 --- /dev/null +++ b/DependencyInjection/Compiler/GraphQLEventListenerPass.php @@ -0,0 +1,33 @@ +has('graphql.event_dispatcher')) { + return; + } + $dispatcher = $container->findDefinition('graphql.event_dispatcher'); + foreach ($container->findTaggedServiceIds('graphql.event_listener') as $id => $tags) { + foreach ($tags as $attributes) { + $event = $attributes['event'] ?? null; + $method = $attributes['method'] ?? '__invoke'; + if ($event) { + $dispatcher->addMethodCall('addListener', [ + $event, + [new Reference($id), $method] + ]); + } + } + } + foreach ($container->findTaggedServiceIds('graphql.event_subscriber') as $id => $tags) { + $dispatcher->addMethodCall('addSubscriber', [new Reference($id)]); + } + } +} 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..16ee919 100644 --- a/DependencyInjection/GraphQLExtension.php +++ b/DependencyInjection/GraphQLExtension.php @@ -4,8 +4,8 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; /** * This is the class that loads and manages your bundle configuration @@ -19,13 +19,13 @@ class GraphQLExtension extends Extension /** * {@inheritdoc} */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $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']; } @@ -46,8 +46,8 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('graphql.security.white_list', $this->config['security']['white_list']); - $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - $loader->load('services.xml'); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $loader->load('services.yaml'); } private function getDefaultHeaders() diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4566d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.4 +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/Container/SymfonyContainer.php b/Execution/Container/SymfonyContainer.php index 3a9e096..42b3cba 100644 --- a/Execution/Container/SymfonyContainer.php +++ b/Execution/Container/SymfonyContainer.php @@ -9,13 +9,12 @@ namespace Youshido\GraphQLBundle\Execution\Container; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Youshido\GraphQL\Execution\Container\ContainerInterface; -class SymfonyContainer implements ContainerInterface, ContainerAwareInterface +class SymfonyContainer implements ContainerInterface { - use ContainerAwareTrait; + public function __construct(private readonly ?\Symfony\Component\DependencyInjection\ContainerInterface $container = null) + {} public function get($id) { @@ -68,4 +67,4 @@ public function getSymfonyContainer() return $this->container; } -} \ No newline at end of file +} 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/GraphQLBundle.php b/GraphQLBundle.php index c49afb1..3facba0 100644 --- a/GraphQLBundle.php +++ b/GraphQLBundle.php @@ -7,23 +7,20 @@ use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\HttpKernel\Bundle\Bundle; use Youshido\GraphQLBundle\DependencyInjection\Compiler\GraphQlCompilerPass; +use Youshido\GraphQLBundle\DependencyInjection\Compiler\GraphQLEventListenerPass; use Youshido\GraphQLBundle\DependencyInjection\GraphQLExtension; class GraphQLBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); $container->addCompilerPass(new GraphQlCompilerPass()); - $container->addCompilerPass( - new RegisterListenersPass( - 'graphql.event_dispatcher', - 'graphql.event_listener', - 'graphql.event_subscriber' - ), - PassConfig::TYPE_BEFORE_REMOVING - ); + // RegisterListenersPass is for the main event dispatcher only in Symfony 7/8 + $container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING); + $container->addCompilerPass(new GraphQLEventListenerPass()); // Register custom event listeners/subscribers + // For custom event dispatchers, register listeners/subscribers via service tags in your YAML/XML config or a custom CompilerPass. } diff --git a/Listener/SampleGraphQLEventListener.php b/Listener/SampleGraphQLEventListener.php new file mode 100644 index 0000000..9a3a0eb --- /dev/null +++ b/Listener/SampleGraphQLEventListener.php @@ -0,0 +1,10 @@ +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..b67ee64 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.4", + "99designs/graphql": "dev-master", + "symfony/security-core": "~7.4", + "symfony/framework-bundle": "~7.4", + "symfony/console": "~7.4" }, "require-dev": { "phpunit/phpunit": "~9.6", - "composer/composer": "~1.2", - "symfony/yaml": "~4.4 || ~5.4" + "rector/rector": "*", + "symfony/yaml": "~7.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, + ]);