Skip to content

Commit c8626cf

Browse files
authored
Merge pull request #237 from andrew-demb/phpstan-v2
📦 Migrate to phpstan v2 (max level), fix SA issues, PHP 8.4 deprecations
2 parents 1945c66 + 11deb0c commit c8626cf

File tree

12 files changed

+82
-79
lines changed

12 files changed

+82
-79
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
matrix:
1414
install-args: ['']
15-
php-version: ['8.2']
15+
php-version: ['8.2', '8.3', '8.4']
1616
fail-fast: false
1717
steps:
1818
# Cancel previous runs of the same branch

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"symfony/yaml": "^6.4 || ^7",
3737
"beberlei/porpaginas": "^1.2 || ^2.0",
3838
"symfony/phpunit-bridge": "^6.4 || ^7",
39-
"phpstan/phpstan": "^1.8",
39+
"phpstan/phpstan": "^2",
40+
"phpstan/phpstan-symfony": "^2.0",
4041
"composer/package-versions-deprecated": "^1.8",
4142
"composer/semver": "^3.4"
4243
},
@@ -47,7 +48,7 @@
4748
"phpdocumentor/type-resolver": "<1.4"
4849
},
4950
"scripts": {
50-
"phpstan": "phpstan analyse -c phpstan.neon --level=7 --no-progress"
51+
"phpstan": "phpstan analyse -c phpstan.neon --no-progress"
5152
},
5253
"suggest": {
5354
"symfony/security-bundle": "To use #[Logged] or #[Right] attributes"

phpstan.neon

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
includes:
2-
- vendor/phpstan/phpstan/conf/bleedingEdge.neon
2+
- vendor/phpstan/phpstan/conf/bleedingEdge.neon
3+
- vendor/phpstan/phpstan-symfony/extension.neon
4+
35
parameters:
6+
level: max
47
tmpDir: .phpstan-cache
58
paths:
6-
- .
9+
- src
710
excludePaths:
811
- vendor
912
- cache
1013
- .phpstan-cache
1114
- tests
12-
level: max
15+
1316
polluteScopeWithLoopInitialAssignments: false
1417
polluteScopeWithAlwaysIterableForeach: false
15-
checkAlwaysTrueCheckTypeFunctionCall: true
16-
checkAlwaysTrueInstanceof: true
17-
checkAlwaysTrueStrictComparison: true
1818
checkExplicitMixedMissingReturn: true
1919
checkFunctionNameCase: true
2020
checkInternalClassCaseSensitivity: true
@@ -24,3 +24,5 @@ parameters:
2424
ignoreErrors:
2525
# Wrong return type hint in Symfony's TreeBuilder
2626
- '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::\w+\(\).#'
27+
# PhpStan doesn't know bundle resolved config structure and it's pretty complex to describe it
28+
- '#Parameter \#2 \$value of method Symfony\\Component\\DependencyInjection\\Container::setParameter\(\) expects array\|bool\|float\|int\|string\|UnitEnum\|null, mixed given\.#'

src/Controller/GraphQL/InvalidUserPasswordException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class InvalidUserPasswordException extends GraphQLException
1010
{
11-
public static function create(Exception $previous = null): self
11+
public static function create(?Exception $previous = null): self
1212
{
1313
return new self('The provided user / password is incorrect.', 401, $previous, ['category' => 'Security']);
1414
}

src/Controller/GraphQLiteController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class GraphQLiteController
4848
*/
4949
private $httpCodeDecider;
5050

51-
public function __construct(ServerConfig $serverConfig, HttpMessageFactoryInterface $httpMessageFactory = null, ?int $debug = null, ?HttpCodeDeciderInterface $httpCodeDecider = null)
51+
public function __construct(ServerConfig $serverConfig, ?HttpMessageFactoryInterface $httpMessageFactory = null, ?int $debug = null, ?HttpCodeDeciderInterface $httpCodeDecider = null)
5252
{
5353
$this->serverConfig = $serverConfig;
5454
$this->httpMessageFactory = $httpMessageFactory ?: new PsrHttpFactory(new ServerRequestFactory(), new StreamFactory(), new UploadedFileFactory(), new ResponseFactory());
@@ -78,7 +78,7 @@ public function handleRequest(Request $request): Response
7878
{
7979
$psr7Request = $this->httpMessageFactory->createRequest($request);
8080

81-
if (strtoupper($request->getMethod()) === "POST" && empty($psr7Request->getParsedBody())) {
81+
if (strtoupper($request->getMethod()) === 'POST' && empty($psr7Request->getParsedBody())) {
8282
$content = $psr7Request->getBody()->getContents();
8383
$parsedBody = json_decode($content, true);
8484
if (json_last_error() !== JSON_ERROR_NONE) {
@@ -94,6 +94,7 @@ public function handleRequest(Request $request): Response
9494
if (class_exists(UploadMiddleware::class)) {
9595
$uploadMiddleware = new UploadMiddleware();
9696
$psr7Request = $uploadMiddleware->processRequest($psr7Request);
97+
\assert($psr7Request instanceof ServerRequestInterface);
9798
}
9899

99100
return $this->handlePsr7Request($psr7Request, $request);

src/DependencyInjection/GraphQLiteCompilerPass.php

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
use function filter_var;
5050
use function ini_get;
5151
use function interface_exists;
52-
use function strpos;
5352

5453
/**
5554
* Detects controllers and types automatically and tag them.
@@ -199,52 +198,53 @@ public function process(ContainerBuilder $container): void
199198
$container->getDefinition(StaticClassListTypeMapperFactory::class)->setArgument(0, $staticTypes);
200199
}
201200

202-
foreach ($container->getDefinitions() as $id => $definition) {
201+
foreach ($container->getDefinitions() as $definition) {
203202
if ($definition->isAbstract() || $definition->getClass() === null) {
204203
continue;
205204
}
206-
/**
207-
* @var class-string $class
208-
*/
205+
206+
/** @var class-string $class */
209207
$class = $definition->getClass();
210-
/* foreach ($controllersNamespaces as $controllersNamespace) {
211-
if (strpos($class, $controllersNamespace) === 0) {
212-
$definition->addTag('graphql.annotated.controller');
213-
}
214-
}*/
215208

216209
foreach ($typesNamespaces as $typesNamespace) {
217-
if (strpos($class, $typesNamespace) === 0) {
218-
//$definition->addTag('graphql.annotated.type');
219-
// Set the types public
220-
$reflectionClass = new ReflectionClass($class);
221-
$typeAnnotation = $this->getAnnotationReader()->getTypeAnnotation($reflectionClass);
222-
if ($typeAnnotation !== null && $typeAnnotation->isSelfType()) {
223-
continue;
224-
}
225-
if ($typeAnnotation !== null || $this->getAnnotationReader()->getExtendTypeAnnotation($reflectionClass) !== null) {
210+
\assert(\is_string($typesNamespace));
211+
212+
if (false === str_starts_with($class, $typesNamespace)) {
213+
continue;
214+
}
215+
216+
// Set the types public
217+
$reflectionClass = new ReflectionClass($class);
218+
$typeAnnotation = $this->getAnnotationReader()->getTypeAnnotation($reflectionClass);
219+
if ($typeAnnotation !== null && $typeAnnotation->isSelfType()) {
220+
continue;
221+
}
222+
if ($typeAnnotation !== null || $this->getAnnotationReader()->getExtendTypeAnnotation($reflectionClass) !== null) {
223+
$definition->setPublic(true);
224+
}
225+
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
226+
$factory = $reader->getFactoryAnnotation($method);
227+
if ($factory !== null) {
226228
$definition->setPublic(true);
227229
}
228-
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
229-
$factory = $reader->getFactoryAnnotation($method);
230-
if ($factory !== null) {
231-
$definition->setPublic(true);
232-
}
233-
}
234230
}
235231
}
236232
}
237233

238234
foreach ($controllersNamespaces as $controllersNamespace) {
235+
\assert(\is_string($controllersNamespace));
236+
239237
$schemaFactory->addMethodCall('addNamespace', [ $controllersNamespace ]);
240-
foreach ($this->getClassList($controllersNamespace) as $className => $refClass) {
238+
foreach ($this->getClassList($controllersNamespace) as $refClass) {
241239
$this->makePublicInjectedServices($refClass, $reader, $container, true);
242240
}
243241
}
244242

245243
foreach ($typesNamespaces as $typeNamespace) {
244+
\assert(\is_string($typeNamespace));
245+
246246
$schemaFactory->addMethodCall('addNamespace', [ $typeNamespace ]);
247-
foreach ($this->getClassList($typeNamespace) as $className => $refClass) {
247+
foreach ($this->getClassList($typeNamespace) as $refClass) {
248248
$this->makePublicInjectedServices($refClass, $reader, $container, false);
249249
}
250250
}
@@ -256,11 +256,14 @@ public function process(ContainerBuilder $container): void
256256
$customNotMappedTypes = [];
257257
foreach ($taggedServices as $id => $tags) {
258258
foreach ($tags as $attributes) {
259-
if (isset($attributes["class"])) {
260-
$phpClass = $attributes["class"];
261-
if (!class_exists($phpClass)) {
259+
\assert(\is_array($attributes));
260+
261+
if (isset($attributes['class']) && is_string($attributes['class'])) {
262+
$phpClass = $attributes['class'];
263+
if (false === class_exists($phpClass)) {
262264
throw new \RuntimeException(sprintf('The class attribute of the graphql.output_type annotation of the %s service must point to an existing PHP class. Value passed: %s', $id, $phpClass));
263265
}
266+
264267
$customTypes[$phpClass] = new Reference($id);
265268
} else {
266269
$customNotMappedTypes[] = new Reference($id);
@@ -308,6 +311,8 @@ private function registerController(string $controllerClassName, ContainerBuilde
308311

309312
$serviceLocatorMap = [];
310313
foreach ($controllersList as $controller) {
314+
\assert(\is_string($controller));
315+
311316
$serviceLocatorMap[$controller] = new Reference($controller);
312317
}
313318

src/DependencyInjection/GraphQLiteExtension.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,35 @@ public function load(array $configs, ContainerBuilder $container): void
3636

3737
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/container'));
3838

39+
\assert(\is_array($config['namespace']));
3940
if (isset($config['namespace']['controllers'])) {
4041
$controllers = $config['namespace']['controllers'];
4142
if (!is_array($controllers)) {
4243
$controllers = [ $controllers ];
4344
}
45+
4446
$namespaceController = array_map(
4547
function($namespace): string {
48+
\assert(\is_string($namespace));
49+
4650
return rtrim($namespace, '\\');
4751
},
4852
$controllers
4953
);
5054
} else {
5155
$namespaceController = [];
5256
}
57+
58+
\assert(\is_array($config['namespace']));
5359
if (isset($config['namespace']['types'])) {
5460
$types = $config['namespace']['types'];
5561
if (!is_array($types)) {
5662
$types = [ $types ];
5763
}
5864
$namespaceType = array_map(
5965
function($namespace): string {
66+
\assert(\is_string($namespace));
67+
6068
return rtrim($namespace, '\\');
6169
},
6270
$types
@@ -65,6 +73,7 @@ function($namespace): string {
6573
$namespaceType = [];
6674
}
6775

76+
\assert(\is_array($config['security']));
6877
$enableLogin = $config['security']['enable_login'] ?? 'auto';
6978
$enableMe = $config['security']['enable_me'] ?? 'auto';
7079

@@ -80,11 +89,15 @@ function($namespace): string {
8089
$loader->load('graphqlite.xml');
8190

8291
$definition = $container->getDefinition(ServerConfig::class);
83-
if (isset($config['debug'])) {
84-
$debugCode = $this->toDebugCode($config['debug']);
92+
if (isset($config['debug']) && \is_array($config['debug'])) {
93+
/** @var array<string, int> $configDebug */
94+
$configDebug = $config['debug'];
95+
96+
$debugCode = $this->toDebugCode($configDebug);
8597
} else {
8698
$debugCode = DebugFlag::RETHROW_UNSAFE_EXCEPTIONS;
8799
}
100+
88101
$definition->addMethodCall('setDebugFlag', [$debugCode]);
89102

90103
$container->registerForAutoconfiguration(ObjectType::class)

src/GraphiQL/EndpointResolver.php

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,20 @@ public function __construct(RequestStack $requestStack)
1919
$this->requestStack = $requestStack;
2020
}
2121

22-
/**
23-
* @return string
24-
*/
25-
public function getBySchema($name)
22+
public function getBySchema($name): string
2623
{
27-
if ('default' === $name) {
28-
$request = $this->requestStack->getCurrentRequest();
29-
assert(!is_null($request));
30-
31-
return $request->getBaseUrl().'/graphql';
24+
if ('default' !== $name) {
25+
/** @phpstan-ignore throw.notThrowable (Missing return type in the library) */
26+
throw GraphQLEndpointInvalidSchemaException::forSchemaAndResolver($name, self::class);
3227
}
3328

34-
throw GraphQLEndpointInvalidSchemaException::forSchemaAndResolver($name, self::class);
29+
$request = $this->requestStack->getCurrentRequest();
30+
assert(!is_null($request));
31+
32+
return $request->getBaseUrl().'/graphql';
3533
}
3634

37-
/**
38-
* @return string
39-
*/
40-
public function getDefault()
35+
public function getDefault(): string
4136
{
4237
return $this->getBySchema('default');
4338
}

src/Mappers/RequestParameter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class RequestParameter implements ParameterInterface
1616
* @param array<string, mixed> $args
1717
* @param mixed $context
1818
*/
19-
public function resolve(?object $source, array $args, $context, ResolveInfo $info): mixed
19+
public function resolve(?object $source, array $args, mixed $context, ResolveInfo $info): mixed
2020
{
2121
if (!$context instanceof SymfonyRequestContextInterface) {
2222
throw new GraphQLException('Cannot type-hint on a Symfony Request object in your query/mutation/field. The request context must implement SymfonyRequestContextInterface.');

src/Security/AuthorizationService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function __construct(?AuthorizationCheckerInterface $authorizationChecker
3030
*
3131
* @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.
3232
*/
33-
public function isAllowed(string $right, $subject = null): bool
33+
public function isAllowed(string $right, mixed $subject = null): bool
3434
{
3535
if ($this->authorizationChecker === null || $this->tokenStorage === null) {
3636
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');

0 commit comments

Comments
 (0)