Skip to content

Commit 56d1865

Browse files
shakaranostrolucky
andauthored
feat: improve phpstan level 7 and add phpstan-symfony rules (#2161)
Co-authored-by: Gabriel Ostrolucký <[email protected]> Co-authored-by: Gabriel Ostrolucký <[email protected]>
1 parent 1865171 commit 56d1865

21 files changed

+168
-101
lines changed

.github/workflows/static-analysis.yml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@ on:
2424
- tests/**
2525

2626
jobs:
27-
static-analysis:
28-
name: "Static Analysis"
29-
uses: "doctrine/.github/.github/workflows/[email protected]"
27+
phpstan:
28+
name: "PHPStan"
29+
runs-on: "ubuntu-latest"
30+
31+
steps:
32+
- name: "Checkout code"
33+
uses: "actions/checkout@v5"
34+
35+
- name: "Install PHP"
36+
uses: "shivammathur/setup-php@v2"
37+
with:
38+
coverage: "none"
39+
php-version: "8.4"
40+
41+
- name: "Install dependencies with Composer"
42+
uses: "ramsey/composer-install@v3"
43+
44+
- name: "Warm up Symfony cache"
45+
run: "php tests/console-application.php"
46+
47+
- name: "Run a static analysis with phpstan/phpstan"
48+
run: "vendor/bin/phpstan analyse -v"

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"phpstan/phpstan": "2.1.1",
4949
"phpstan/phpstan-phpunit": "2.0.3",
5050
"phpstan/phpstan-strict-rules": "^2",
51+
"phpstan/phpstan-symfony": "^2.0",
5152
"phpunit/phpunit": "^12.3.10",
5253
"psr/log": "^3.0",
5354
"symfony/doctrine-messenger": "^6.4 || ^7.0 || ^8.0",

phpstan.neon.dist

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
includes:
2+
- vendor/phpstan/phpstan-symfony/extension.neon
13
parameters:
2-
level: 6
4+
level: 7
35
reportUnmatchedIgnoredErrors: true
46
paths:
57
- config
68
- src
79
- tests
8-
ignoreErrors:
10+
scanFiles:
11+
- vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php
12+
symfony:
13+
constantHassers: false
14+
containerXmlPath: var/cache/test/Doctrine_Bundle_DoctrineBundle_Tests_DependencyInjection_Fixtures_TestKernelTestDebugContainer.xml

src/Command/DoctrineCommand.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Doctrine\Persistence\ManagerRegistry;
99
use Symfony\Component\Console\Command\Command;
1010

11+
use function assert;
12+
1113
/**
1214
* Base class for Doctrine console commands to extend from.
1315
*
@@ -26,7 +28,10 @@ public function __construct(
2628
*/
2729
protected function getDoctrineConnection(string $name): Connection
2830
{
29-
return $this->getDoctrine()->getConnection($name);
31+
$connection = $this->getDoctrine()->getConnection($name);
32+
assert($connection instanceof Connection);
33+
34+
return $connection;
3035
}
3136

3237
protected function getDoctrine(): ManagerRegistry

src/ConnectionFactory.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
/**
2828
* @internal This class is not meant to be used outside this bundle
2929
*
30-
* @phpstan-import-type Params from DriverManager
30+
* @phpstan-type Params = array<string, mixed>
3131
*/
3232
final class ConnectionFactory
3333
{
@@ -44,7 +44,6 @@ final class ConnectionFactory
4444
'sqlite3' => 'pdo_sqlite',
4545
];
4646

47-
/** @phpstan-ignore property.onlyWritten */
4847
private readonly DsnParser $dsnParser;
4948

5049
private bool $initialized = false;
@@ -60,7 +59,6 @@ public function __construct(
6059
/**
6160
* Create a connection by name.
6261
*
63-
* @param mixed[] $params
6462
* @param array<string, string> $mappingTypes
6563
* @phpstan-param Params $params
6664
*/
@@ -86,7 +84,6 @@ public function createConnection(
8684
}
8785
}
8886

89-
/** @phpstan-ignore-next-line We should adjust when https://github.com/phpstan/phpstan/issues/12414 is fixed */
9087
if (! isset($params['pdo']) && (! isset($params['charset']) || isset($params['dbname_suffix']))) {
9188
$wrapperClass = null;
9289

@@ -213,22 +210,18 @@ private function addDatabaseSuffix(array $params): array
213210
* @param mixed[] $params The list of parameters.
214211
* @phpstan-param Params $params
215212
*
216-
* @return mixed[] A modified list of parameters with info from a database
213+
* @return Params params A modified list of parameters with info from a database
217214
* URL extracted into individual parameter parts.
218215
* @phpstan-return Params
219216
*
220217
* @throws DBALException
221-
*
222-
* @phpstan-ignore throws.unusedType
223218
*/
224219
private function parseDatabaseUrl(array $params): array
225220
{
226-
/** @phpstan-ignore isset.offset (for DBAL < 4) */
227221
if (! isset($params['url'])) {
228222
return $params;
229223
}
230224

231-
/** @phpstan-ignore deadCode.unreachable */
232225
try {
233226
$parsedParams = $this->dsnParser->parse($params['url']);
234227
} catch (MalformedDsnException $e) {

src/DataCollector/DoctrineDataCollector.php

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,19 @@
4646
* }>>,
4747
* entityCounts: array<string, array<class-string, int>>
4848
* }
49-
* @psalm-property DataType $data
49+
* @phpstan-type GroupedQueryItemType = array{
50+
* executionMS: float,
51+
* explainable: bool,
52+
* sql: string,
53+
* params: ?array<array-key, mixed>,
54+
* runnable: bool,
55+
* types: ?array<array-key, Type|int|string|null>,
56+
* count: int,
57+
* index: int,
58+
* executionPercent?: float
59+
* }
60+
* @phpstan-type GroupedQueriesType = array<string, array<int, GroupedQueryItemType>>
61+
* @phpstan-property DataType $data
5062
*/
5163
class DoctrineDataCollector extends BaseCollector
5264
{
@@ -56,17 +68,7 @@ class DoctrineDataCollector extends BaseCollector
5668

5769
/**
5870
* @var mixed[][]|null
59-
* @phpstan-var ?array<string, list<array{
60-
* executionMS: float,
61-
* explainable: bool,
62-
* sql: string,
63-
* params: ?array<array-key, mixed>,
64-
* runnable: bool,
65-
* types: ?array<array-key, Type|int|string|null>,
66-
* count: int,
67-
* index: int,
68-
* executionPercent?: float
69-
* }>>
71+
* @phpstan-var ?GroupedQueriesType
7072
*/
7173
private array|null $groupedQueries = null;
7274

@@ -269,17 +271,7 @@ public function getManagedEntityCountByClass(): array
269271

270272
/**
271273
* @return string[][]
272-
* @phpstan-return array<string, list<array{
273-
* executionMS: float,
274-
* explainable: bool,
275-
* sql: string,
276-
* params: ?array<array-key, mixed>,
277-
* runnable: bool,
278-
* types: ?array<array-key, Type|int|string|null>,
279-
* count: int,
280-
* index: int,
281-
* executionPercent?: float
282-
* }>>
274+
* @phpstan-return GroupedQueriesType
283275
*/
284276
public function getGroupedQueries(): array
285277
{

src/Dbal/ManagerRegistryAwareConnectionProvider.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
99
use Doctrine\Persistence\AbstractManagerRegistry;
1010

11+
use function assert;
12+
1113
class ManagerRegistryAwareConnectionProvider implements ConnectionProvider
1214
{
1315
public function __construct(
@@ -17,11 +19,17 @@ public function __construct(
1719

1820
public function getDefaultConnection(): Connection
1921
{
20-
return $this->managerRegistry->getConnection();
22+
$connection = $this->managerRegistry->getConnection();
23+
assert($connection instanceof Connection);
24+
25+
return $connection;
2126
}
2227

2328
public function getConnection(string $name): Connection
2429
{
25-
return $this->managerRegistry->getConnection($name);
30+
$connection = $this->managerRegistry->getConnection($name);
31+
assert($connection instanceof Connection);
32+
33+
return $connection;
2634
}
2735
}

src/DependencyInjection/Configuration.php

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use RuntimeException;
1313
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
1414
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
15-
use Symfony\Component\Config\Definition\Builder\NodeParentInterface;
1615
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1716
use Symfony\Component\Config\Definition\ConfigurationInterface;
1817
use Symfony\Component\DependencyInjection\Exception\LogicException;
@@ -49,7 +48,6 @@ public function __construct(private bool $debug)
4948
{
5049
}
5150

52-
/** @return TreeBuilder<'array'> */
5351
public function getConfigTreeBuilder(): TreeBuilder
5452
{
5553
$treeBuilder = new TreeBuilder('doctrine');
@@ -63,14 +61,13 @@ public function getConfigTreeBuilder(): TreeBuilder
6361

6462
/**
6563
* Add DBAL section to configuration tree
66-
*
67-
* @param ArrayNodeDefinition<TreeBuilder<'array'>> $node
6864
*/
6965
private function addDbalSection(ArrayNodeDefinition $node): void
7066
{
7167
// Key that should not be rewritten to the connection config
7268
$excludedKeys = ['default_connection' => true, 'driver_schemes' => true, 'driver_scheme' => true, 'types' => true, 'type' => true];
7369

70+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
7471
$node
7572
->children()
7673
->arrayNode('dbal')
@@ -157,8 +154,6 @@ private function addDbalSection(ArrayNodeDefinition $node): void
157154

158155
/**
159156
* Return the dbal connections node
160-
*
161-
* @return ArrayNodeDefinition<TreeBuilder<'array'>>
162157
*/
163158
private function getDbalConnectionsNode(): ArrayNodeDefinition
164159
{
@@ -172,6 +167,7 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition
172167

173168
$this->configureDbalDriverNode($connectionNode);
174169

170+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
175171
$connectionNode
176172
->fixXmlConfig('option')
177173
->fixXmlConfig('mapping_type')
@@ -218,6 +214,7 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition
218214
->scalarNode('result_cache')->end()
219215
->end();
220216

217+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
221218
$replicaNode = $connectionNode
222219
->children()
223220
->arrayNode('replicas')
@@ -232,13 +229,10 @@ private function getDbalConnectionsNode(): ArrayNodeDefinition
232229
* Adds config keys related to params processed by the DBAL drivers
233230
*
234231
* These keys are available for replica configurations too.
235-
*
236-
* @param ArrayNodeDefinition<TP> $node
237-
*
238-
* @template TP of NodeParentInterface|null
239-
**/
232+
*/
240233
private function configureDbalDriverNode(ArrayNodeDefinition $node): void
241234
{
235+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
242236
$node
243237
->validate()
244238
->always(static function (array $values) {
@@ -364,8 +358,6 @@ private function configureDbalDriverNode(ArrayNodeDefinition $node): void
364358

365359
/**
366360
* Add the ORM section to configuration tree
367-
*
368-
* @param ArrayNodeDefinition<TreeBuilder<'array'>> $node
369361
*/
370362
private function addOrmSection(ArrayNodeDefinition $node): void
371363
{
@@ -378,6 +370,7 @@ private function addOrmSection(ArrayNodeDefinition $node): void
378370
'controller_resolver' => true,
379371
];
380372

373+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
381374
$node
382375
->children()
383376
->arrayNode('orm')
@@ -525,6 +518,7 @@ private function getOrmEntityListenersNode(): NodeDefinition
525518
return ['entities' => $entities];
526519
};
527520

521+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
528522
$node
529523
->beforeNormalization()
530524
// Yaml normalization
@@ -564,14 +558,13 @@ private function getOrmEntityListenersNode(): NodeDefinition
564558

565559
/**
566560
* Return ORM entity manager node
567-
*
568-
* @return ArrayNodeDefinition<TreeBuilder<'array'>>
569561
*/
570562
private function getOrmEntityManagersNode(): ArrayNodeDefinition
571563
{
572564
$treeBuilder = new TreeBuilder('entity_managers');
573565
$node = $treeBuilder->getRootNode();
574566

567+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
575568
$node
576569
->requiresAtLeastOneElement()
577570
->useAttributeAsKey('name')
@@ -740,14 +733,13 @@ private function getOrmEntityManagersNode(): ArrayNodeDefinition
740733

741734
/**
742735
* Return an ORM cache driver node for a given entity manager
743-
*
744-
* @return ArrayNodeDefinition<TreeBuilder<'array'>>
745736
*/
746737
private function getOrmCacheDriverNode(string $name): ArrayNodeDefinition
747738
{
748739
$treeBuilder = new TreeBuilder($name);
749740
$node = $treeBuilder->getRootNode();
750741

742+
/** @phpstan-ignore class.notFound (Phpstan Symfony extension does not know yet how to deal with these) */
751743
$node
752744
->beforeNormalization()
753745
->ifString()

0 commit comments

Comments
 (0)