Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3912e20

Browse files
authoredJan 4, 2022
Merge branch 'master' into generic-query
2 parents 2abd21c + 9f40e7f commit 3912e20

27 files changed

+634
-72
lines changed
 

‎.github/workflows/build.yml

+33-17
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ jobs:
1414
runs-on: "ubuntu-latest"
1515

1616
strategy:
17+
fail-fast: false
1718
matrix:
1819
php-version:
1920
- "7.1"
2021
- "7.2"
2122
- "7.3"
2223
- "7.4"
2324
- "8.0"
25+
- "8.1"
2426

2527
steps:
2628
- name: "Checkout"
@@ -35,12 +37,16 @@ jobs:
3537
- name: "Validate Composer"
3638
run: "composer validate"
3739

38-
- name: "Install dependencies"
39-
run: "composer install --no-interaction --no-progress --no-suggest"
40-
4140
- name: "Downgrade PHPUnit"
4241
if: matrix.php-version == '7.1' || matrix.php-version == '7.2' || matrix.php-version == '7.3'
43-
run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
42+
run: "composer require --dev phpunit/phpunit:^7.5.20 --no-update --update-with-dependencies"
43+
44+
- name: "Downgrade Doctrine ORM"
45+
if: matrix.php-version == '7.1'
46+
run: "composer require --dev doctrine/orm:^2.7.5 doctrine/lexer:^1.0 --no-update --update-with-dependencies"
47+
48+
- name: "Install dependencies"
49+
run: "composer install --no-interaction --no-progress"
4450

4551
- name: "Lint"
4652
run: "make lint"
@@ -64,7 +70,7 @@ jobs:
6470
run: "composer validate"
6571

6672
- name: "Install dependencies"
67-
run: "composer install --no-interaction --no-progress --no-suggest"
73+
run: "composer install --no-interaction --no-progress"
6874

6975
- name: "Lint"
7076
run: "make lint"
@@ -85,6 +91,7 @@ jobs:
8591
- "7.3"
8692
- "7.4"
8793
- "8.0"
94+
- "8.1"
8895
dependencies:
8996
- "lowest"
9097
- "highest"
@@ -99,17 +106,21 @@ jobs:
99106
coverage: "none"
100107
php-version: "${{ matrix.php-version }}"
101108

109+
- name: "Downgrade PHPUnit"
110+
if: matrix.php-version == '7.1' || matrix.php-version == '7.2' || matrix.php-version == '7.3'
111+
run: "composer require --dev phpunit/phpunit:^7.5.20 --no-update --update-with-dependencies"
112+
113+
- name: "Downgrade Doctrine ORM"
114+
if: matrix.php-version == '7.1'
115+
run: "composer require --dev doctrine/orm:^2.7.5 doctrine/lexer:^1.0 --no-update --update-with-dependencies"
116+
102117
- name: "Install lowest dependencies"
103118
if: ${{ matrix.dependencies == 'lowest' }}
104-
run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
119+
run: "composer update --prefer-lowest --no-interaction --no-progress"
105120

106121
- name: "Install highest dependencies"
107122
if: ${{ matrix.dependencies == 'highest' }}
108-
run: "composer update --no-interaction --no-progress --no-suggest"
109-
110-
- name: "Downgrade PHPUnit"
111-
if: matrix.php-version == '7.1' || matrix.php-version == '7.2' || matrix.php-version == '7.3'
112-
run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
123+
run: "composer update --no-interaction --no-progress"
113124

114125
- name: "Tests"
115126
run: "make tests"
@@ -127,6 +138,7 @@ jobs:
127138
- "7.3"
128139
- "7.4"
129140
- "8.0"
141+
- "8.1"
130142
dependencies:
131143
- "lowest"
132144
- "highest"
@@ -143,17 +155,21 @@ jobs:
143155
extensions: mbstring
144156
tools: composer:v2
145157

158+
- name: "Downgrade PHPUnit"
159+
if: matrix.php-version == '7.1' || matrix.php-version == '7.2' || matrix.php-version == '7.3'
160+
run: "composer require --dev phpunit/phpunit:^7.5.20 --no-update --update-with-dependencies"
161+
162+
- name: "Downgrade Doctrine ORM"
163+
if: matrix.php-version == '7.1'
164+
run: "composer require --dev doctrine/orm:^2.7.5 doctrine/lexer:^1.0 --no-update --update-with-dependencies"
165+
146166
- name: "Install lowest dependencies"
147167
if: ${{ matrix.dependencies == 'lowest' }}
148-
run: "composer update --prefer-lowest --no-interaction --no-progress --no-suggest"
168+
run: "composer update --prefer-lowest --no-interaction --no-progress"
149169

150170
- name: "Install highest dependencies"
151171
if: ${{ matrix.dependencies == 'highest' }}
152-
run: "composer update --no-interaction --no-progress --no-suggest"
153-
154-
- name: "Downgrade PHPUnit"
155-
if: matrix.php-version == '7.1' || matrix.php-version == '7.2' || matrix.php-version == '7.3'
156-
run: "composer require --dev phpunit/phpunit:^7.5.20 --update-with-dependencies"
172+
run: "composer update --no-interaction --no-progress"
157173

158174
- name: "PHPStan"
159175
run: "make phpstan"

‎build-cs/composer.json

+5
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,10 @@
33
"consistence-community/coding-standard": "^3.10",
44
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
55
"slevomat/coding-standard": "^6.4"
6+
},
7+
"config": {
8+
"allow-plugins": {
9+
"dealerdirect/phpcodesniffer-composer-installer": true
10+
}
611
}
712
}

‎composer.json

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
],
88
"require": {
99
"php": "^7.1 || ^8.0",
10-
"phpstan/phpstan": "^1.0"
10+
"phpstan/phpstan": "^1.3"
1111
},
1212
"conflict": {
1313
"doctrine/collections": "<1.0",
@@ -21,23 +21,28 @@
2121
"doctrine/collections": "^1.6",
2222
"doctrine/common": "^2.7 || ^3.0",
2323
"doctrine/dbal": "^2.13.1",
24+
"doctrine/lexer": "^1.2.1",
2425
"doctrine/mongodb-odm": "^1.3 || ^2.1",
2526
"doctrine/orm": "^2.9.1",
2627
"doctrine/persistence": "^1.1 || ^2.0",
2728
"nesbot/carbon": "^2.49",
28-
"nikic/php-parser": "^4.13.0",
29+
"nikic/php-parser": "^4.13.2",
2930
"php-parallel-lint/php-parallel-lint": "^1.2",
3031
"phpstan/phpstan-phpunit": "^1.0",
3132
"phpstan/phpstan-strict-rules": "^1.0",
32-
"phpunit/phpunit": "^9.5",
33-
"ramsey/uuid-doctrine": "^1.5.0"
33+
"phpunit/phpunit": "^9.5.10",
34+
"ramsey/uuid-doctrine": "^1.5.0",
35+
"symfony/cache": "^4.4.35"
3436
},
3537
"config": {
3638
"platform": {
37-
"php": "7.3.24",
38-
"ext-mongo": "1.6.16"
39+
"ext-mongo": "1.12",
40+
"ext-mongodb": "1.6.16"
3941
},
40-
"sort-packages": true
42+
"sort-packages": true,
43+
"allow-plugins": {
44+
"composer/package-versions-deprecated": true
45+
}
4146
},
4247
"extra": {
4348
"branch-alias": {

‎extension.neon

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ parameters:
99
# setting this to false might lead to performance problems
1010
# it changes the braching logic - with false, queryBuilders from all branches are analysed separately
1111
queryBuilderFastAlgorithm: true
12+
featureToggles:
13+
skipCheckGenericClasses:
14+
- Doctrine\ODM\MongoDB\Mapping\ClassMetadata
15+
- Doctrine\ORM\Mapping\ClassMetadata
16+
- Doctrine\ORM\Mapping\ClassMetadataInfo
17+
- Doctrine\Persistence\Mapping\ClassMetadata
1218
stubFiles:
13-
- stubs/ClassMetadataInfo.stub
1419
- stubs/Criteria.stub
1520
- stubs/DocumentManager.stub
1621
- stubs/DocumentRepository.stub
@@ -20,6 +25,7 @@ parameters:
2025
- stubs/EntityRepository.stub
2126
- stubs/ServiceEntityRepository.stub
2227
- stubs/MongoClassMetadataInfo.stub
28+
2329
- stubs/Persistence/ManagerRegistry.stub
2430
- stubs/Persistence/ObjectManager.stub
2531
- stubs/Persistence/ObjectManagerDecorator.stub
@@ -30,7 +36,10 @@ parameters:
3036
- stubs/Collections/Selectable.stub
3137
- stubs/ORM/QueryBuilder.stub
3238
- stubs/ORM/AbstractQuery.stub
33-
- stubs/ORM/Query.stub
39+
- stubs/ORM/Mapping/ClassMetadata.stub
40+
- stubs/ORM/Mapping/ClassMetadataInfo.stub
41+
- stubs/ORM/Query.stub
42+
- stubs/Persistence/Mapping/ClassMetadata.stub
3443
- stubs/ServiceDocumentRepository.stub
3544

3645
parametersSchema:

‎phpstan-baseline.neon

-12
This file was deleted.

‎phpstan.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
includes:
22
- extension.neon
33
- rules.neon
4-
- phpstan-baseline.neon
54
- vendor/phpstan/phpstan-strict-rules/rules.neon
65
- vendor/phpstan/phpstan-phpunit/extension.neon
76
- vendor/phpstan/phpstan-phpunit/rules.neon
@@ -10,6 +9,7 @@ includes:
109
parameters:
1110
excludePaths:
1211
- tests/*/data/*
12+
- tests/*/data-php-*/*
1313

1414
ignoreErrors:
1515
-

‎src/Rules/Doctrine/ORM/PropertiesExtension.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function isAlwaysRead(PropertyReflection $property, string $propertyName)
3030

3131
/**
3232
* @param class-string $className
33+
* @return \Doctrine\ORM\Mapping\ClassMetadataInfo<object>
3334
*/
3435
private function findMetadata(string $className): ?\Doctrine\ORM\Mapping\ClassMetadataInfo
3536
{

‎src/Type/Doctrine/ObjectMetadataResolver.php

+15-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Doctrine\Persistence\ObjectManager;
66
use PHPStan\Reflection\ReflectionProvider;
7-
use function file_exists;
7+
use function is_file;
88
use function is_readable;
99

1010
final class ObjectMetadataResolver
@@ -60,11 +60,18 @@ public function getObjectManager(): ?ObjectManager
6060

6161
private function loadObjectManager(string $objectManagerLoader): ?ObjectManager
6262
{
63-
if (
64-
!file_exists($objectManagerLoader)
65-
|| !is_readable($objectManagerLoader)
66-
) {
67-
throw new \PHPStan\ShouldNotHappenException('Object manager could not be loaded');
63+
if (!is_file($objectManagerLoader)) {
64+
throw new \PHPStan\ShouldNotHappenException(sprintf(
65+
'Object manager could not be loaded: file "%s" does not exist',
66+
$objectManagerLoader
67+
));
68+
}
69+
70+
if (!is_readable($objectManagerLoader)) {
71+
throw new \PHPStan\ShouldNotHappenException(sprintf(
72+
'Object manager could not be loaded: file "%s" is not readable',
73+
$objectManagerLoader
74+
));
6875
}
6976

7077
return require $objectManagerLoader;
@@ -108,14 +115,14 @@ public function getRepositoryClass(string $className): string
108115

109116
$ormMetadataClass = 'Doctrine\ORM\Mapping\ClassMetadata';
110117
if ($metadata instanceof $ormMetadataClass) {
111-
/** @var \Doctrine\ORM\Mapping\ClassMetadata $ormMetadata */
118+
/** @var \Doctrine\ORM\Mapping\ClassMetadata<object> $ormMetadata */
112119
$ormMetadata = $metadata;
113120
return $ormMetadata->customRepositoryClassName ?? $this->getResolvedRepositoryClass();
114121
}
115122

116123
$odmMetadataClass = 'Doctrine\ODM\MongoDB\Mapping\ClassMetadata';
117124
if ($metadata instanceof $odmMetadataClass) {
118-
/** @var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $odmMetadata */
125+
/** @var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata<object> $odmMetadata */
119126
$odmMetadata = $metadata;
120127
return $odmMetadata->customRepositoryClassName ?? $this->getResolvedRepositoryClass();
121128
}

‎src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ private function findClassNode(string $className, array $nodes): ?Class_
210210
foreach ($nodes as $node) {
211211
if (
212212
$node instanceof Class_
213+
&& $node->namespacedName !== null
213214
&& $node->namespacedName->toString() === $className
214215
) {
215216
return $node;

‎stubs/ClassMetadataInfo.stub

-17
This file was deleted.

‎stubs/MongoClassMetadataInfo.stub

+31-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,40 @@
22

33
namespace Doctrine\ODM\MongoDB\Mapping;
44

5-
class ClassMetadata
5+
use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
6+
use ReflectionClass;
7+
8+
/**
9+
* @template-covariant T of object
10+
* @template-implements BaseClassMetadata<T>
11+
*/
12+
class ClassMetadata implements BaseClassMetadata
613
{
714

815
/** @var string|null */
916
public $customRepositoryClassName;
1017

18+
/**
19+
* @var class-string<T>
20+
*/
21+
public $name;
22+
23+
/**
24+
* @param class-string<T> $documentName
25+
*/
26+
public function __construct(string $documentName)
27+
{
28+
29+
}
30+
31+
/**
32+
* @return class-string<T>
33+
*/
34+
public function getName();
35+
36+
/**
37+
* @return ReflectionClass<T>
38+
*/
39+
public function getReflectionClass();
40+
1141
}

‎stubs/ORM/Mapping/ClassMetadata.stub

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Doctrine\ORM\Mapping;
4+
5+
/**
6+
* @template-covariant T of object
7+
* @extends ClassMetadataInfo<T>
8+
*/
9+
class ClassMetadata extends ClassMetadataInfo
10+
{
11+
/**
12+
* @param class-string<T> $entityName
13+
*/
14+
public function __construct($entityName)
15+
{
16+
17+
}
18+
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Doctrine\ORM\Mapping;
4+
5+
use Doctrine\Persistence\Mapping\ClassMetadata;
6+
use ReflectionClass;
7+
8+
/**
9+
* @template-covariant T of object
10+
* @implements ClassMetadata<T>
11+
*/
12+
class ClassMetadataInfo implements ClassMetadata
13+
{
14+
15+
/** @var string|null */
16+
public $customRepositoryClassName;
17+
18+
/**
19+
* @param class-string<T> $entityName
20+
*/
21+
public function __construct($entityName)
22+
{
23+
24+
}
25+
26+
/**
27+
* @param string $assocName
28+
* @return class-string
29+
*/
30+
public function getAssociationTargetClass($assocName);
31+
32+
/**
33+
* @return T
34+
*/
35+
public function newInstance()
36+
{
37+
38+
}
39+
40+
/**
41+
* @return class-string<T>
42+
*/
43+
public function getName();
44+
45+
/**
46+
* @return ReflectionClass<T>
47+
*/
48+
public function getReflectionClass();
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Doctrine\Persistence\Mapping;
4+
5+
use ReflectionClass;
6+
7+
/**
8+
* @template-covariant T of object
9+
*/
10+
interface ClassMetadata
11+
{
12+
/**
13+
* @return class-string<T>
14+
*/
15+
public function getName();
16+
17+
/**
18+
* @return ReflectionClass<T>
19+
*/
20+
public function getReflectionClass();
21+
}

‎tests/DoctrineIntegration/ODM/document-manager.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
<?php declare(strict_types = 1);
22

33
use Doctrine\Common\Annotations\AnnotationReader;
4-
use Doctrine\Common\Cache\ArrayCache;
54
use Doctrine\ODM\MongoDB\Configuration;
65
use Doctrine\ODM\MongoDB\DocumentManager;
76
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
7+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
8+
use Symfony\Component\Cache\DoctrineProvider;
89

910
$config = new Configuration();
1011
$config->setProxyDir(__DIR__);
1112
$config->setProxyNamespace('PHPstan\Doctrine\OdmProxies');
12-
$config->setMetadataCacheImpl(new ArrayCache());
13+
$config->setMetadataCacheImpl(new DoctrineProvider(new ArrayAdapter()));
1314
$config->setHydratorDir(__DIR__);
1415
$config->setHydratorNamespace('PHPstan\Doctrine\OdmHydrators');
1516

‎tests/DoctrineIntegration/ORM/EntityManagerIntegrationTest.php

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public function dataTopics(): array
1414
{
1515
return [
1616
['entityManagerDynamicReturn'],
17-
['entityRepositoryDynamicReturn'],
1817
['entityManagerMergeReturn'],
1918
['customRepositoryUsage'],
2019
['queryBuilder'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DoctrineIntegration\ORM;
4+
5+
use PHPStan\Testing\LevelsTestCase;
6+
7+
final class EntityRepositoryDynamicReturnIntegrationTest extends LevelsTestCase
8+
{
9+
10+
/**
11+
* @return string[][]
12+
*/
13+
public function dataTopics(): array
14+
{
15+
return [
16+
['entityRepositoryDynamicReturn'],
17+
];
18+
}
19+
20+
public function getDataPath(): string
21+
{
22+
if (PHP_MAJOR_VERSION === 7 && PHP_MINOR_VERSION === 1) {
23+
return __DIR__ . '/data-php-7.1';
24+
}
25+
26+
return __DIR__ . '/data';
27+
}
28+
29+
public function getPhpStanExecutablePath(): string
30+
{
31+
return __DIR__ . '/../../../vendor/phpstan/phpstan/phpstan';
32+
}
33+
34+
public function getPhpStanConfigPath(): string
35+
{
36+
return __DIR__ . '/phpstan.neon';
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"message": "Call to method Doctrine\\ORM\\EntityRepository<PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity>::findOneBy() - entity PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity does not have a field named $blah.",
4+
"line": 94,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Call to method Doctrine\\ORM\\EntityRepository<PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity>::findBy() - entity PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity does not have a field named $blah.",
9+
"line": 116,
10+
"ignorable": true
11+
},
12+
{
13+
"message": "Method Doctrine\\ORM\\EntityRepository<object>::createQueryBuilder() invoked with 0 parameters, 1-2 required.",
14+
"line": 239,
15+
"ignorable": true
16+
},
17+
{
18+
"message": "Could not analyse QueryBuilder with unknown beginning.",
19+
"line": 241,
20+
"ignorable": true
21+
}
22+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
4+
"line": 89,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
9+
"line": 101,
10+
"ignorable": true
11+
},
12+
{
13+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
14+
"line": 110,
15+
"ignorable": true
16+
},
17+
{
18+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
19+
"line": 120,
20+
"ignorable": true
21+
},
22+
{
23+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
24+
"line": 142,
25+
"ignorable": true
26+
},
27+
{
28+
"message": "Call to an undefined method Doctrine\\ORM\\EntityRepository<PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity>::findByNonexistent().",
29+
"line": 148,
30+
"ignorable": true
31+
},
32+
{
33+
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity::doSomethingElse().",
34+
"line": 160,
35+
"ignorable": true
36+
},
37+
{
38+
"message": "Call to an undefined method Doctrine\\ORM\\EntityRepository<PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity>::findOneByNonexistent().",
39+
"line": 165,
40+
"ignorable": true
41+
},
42+
{
43+
"message": "Call to an undefined method Doctrine\\ORM\\EntityRepository<PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\MyEntity>::countByNonexistent().",
44+
"line": 174,
45+
"ignorable": true
46+
}
47+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "Strict comparison using === between int and 'foo' will always evaluate to false.",
4+
"line": 171,
5+
"ignorable": true
6+
}
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "Parameter #1 $className of method Doctrine\\Persistence\\ObjectManager::getRepository() expects class-string<nonexistentClass>, string given.",
4+
"line": 212,
5+
"ignorable": true
6+
}
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"message": "Property PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\Example::$repository with generic class Doctrine\\ORM\\EntityRepository does not specify its types: TEntityClass",
4+
"line": 16,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Class PHPStan\\DoctrineIntegration\\ORM\\EntityRepositoryDynamicReturn\\Bug180Repository extends generic class Doctrine\\ORM\\EntityRepository but does not specify its types: TEntityClass",
9+
"line": 232,
10+
"ignorable": true
11+
}
12+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[
2+
{
3+
"message": "Call to an undefined method object::doSomething().",
4+
"line": 31,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Call to an undefined method object::doSomethingElse().",
9+
"line": 32,
10+
"ignorable": true
11+
},
12+
{
13+
"message": "Call to an undefined method object::doSomething().",
14+
"line": 43,
15+
"ignorable": true
16+
},
17+
{
18+
"message": "Call to an undefined method object::doSomethingElse().",
19+
"line": 44,
20+
"ignorable": true
21+
},
22+
{
23+
"message": "Call to an undefined method object::doSomething().",
24+
"line": 52,
25+
"ignorable": true
26+
},
27+
{
28+
"message": "Call to an undefined method object::doSomethingElse().",
29+
"line": 53,
30+
"ignorable": true
31+
},
32+
{
33+
"message": "Call to an undefined method object::doSomething().",
34+
"line": 62,
35+
"ignorable": true
36+
},
37+
{
38+
"message": "Call to an undefined method object::doSomethingElse().",
39+
"line": 63,
40+
"ignorable": true
41+
}
42+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "Cannot cast mixed to int.",
4+
"line": 241,
5+
"ignorable": true
6+
}
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DoctrineIntegration\ORM\EntityRepositoryDynamicReturn;
4+
5+
use Doctrine\ORM\EntityManager;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use Doctrine\ORM\EntityRepository;
8+
use Doctrine\ORM\Mapping as ORM;
9+
use RuntimeException;
10+
11+
class Example
12+
{
13+
/**
14+
* @var EntityRepository
15+
*/
16+
private $repository;
17+
18+
public function __construct(EntityManagerInterface $entityManager)
19+
{
20+
$this->repository = $entityManager->getRepository(MyEntity::class);
21+
}
22+
23+
public function findDynamicType(): void
24+
{
25+
$test = $this->repository->find(1);
26+
27+
if ($test === null) {
28+
throw new RuntimeException('Sorry, but no...');
29+
}
30+
31+
$test->doSomething();
32+
$test->doSomethingElse();
33+
}
34+
35+
public function findOneByDynamicType(): void
36+
{
37+
$test = $this->repository->findOneBy(['blah' => 'testing']);
38+
39+
if ($test === null) {
40+
throw new RuntimeException('Sorry, but no...');
41+
}
42+
43+
$test->doSomething();
44+
$test->doSomethingElse();
45+
}
46+
47+
public function findAllDynamicType(): void
48+
{
49+
$items = $this->repository->findAll();
50+
51+
foreach ($items as $test) {
52+
$test->doSomething();
53+
$test->doSomethingElse();
54+
}
55+
}
56+
57+
public function findByDynamicType(): void
58+
{
59+
$items = $this->repository->findBy(['blah' => 'testing']);
60+
61+
foreach ($items as $test) {
62+
$test->doSomething();
63+
$test->doSomethingElse();
64+
}
65+
}
66+
}
67+
68+
class Example2
69+
{
70+
/**
71+
* @var EntityRepository<MyEntity>
72+
*/
73+
private $repository;
74+
75+
public function __construct(EntityManagerInterface $entityManager)
76+
{
77+
$this->repository = $entityManager->getRepository(MyEntity::class);
78+
}
79+
80+
public function findDynamicType(): void
81+
{
82+
$test = $this->repository->find(1);
83+
84+
if ($test === null) {
85+
throw new RuntimeException('Sorry, but no...');
86+
}
87+
88+
$test->doSomething();
89+
$test->doSomethingElse();
90+
}
91+
92+
public function findOneByDynamicType(): void
93+
{
94+
$test = $this->repository->findOneBy(['blah' => 'testing']);
95+
96+
if ($test === null) {
97+
throw new RuntimeException('Sorry, but no...');
98+
}
99+
100+
$test->doSomething();
101+
$test->doSomethingElse();
102+
}
103+
104+
public function findAllDynamicType(): void
105+
{
106+
$items = $this->repository->findAll();
107+
108+
foreach ($items as $test) {
109+
$test->doSomething();
110+
$test->doSomethingElse();
111+
}
112+
}
113+
114+
public function findByDynamicType(): void
115+
{
116+
$items = $this->repository->findBy(['blah' => 'testing']);
117+
118+
foreach ($items as $test) {
119+
$test->doSomething();
120+
$test->doSomethingElse();
121+
}
122+
}
123+
}
124+
125+
class Example3MagicMethods
126+
{
127+
/**
128+
* @var EntityRepository<MyEntity>
129+
*/
130+
private $repository;
131+
132+
public function __construct(EntityManagerInterface $entityManager)
133+
{
134+
$this->repository = $entityManager->getRepository(MyEntity::class);
135+
}
136+
137+
public function findDynamicType(): void
138+
{
139+
$test = $this->repository->findById(1);
140+
foreach ($test as $item) {
141+
$item->doSomething();
142+
$item->doSomethingElse();
143+
}
144+
}
145+
146+
public function findDynamicType2(): void
147+
{
148+
$test = $this->repository->findByNonexistent(1);
149+
}
150+
151+
public function findOneByDynamicType(): void
152+
{
153+
$test = $this->repository->findOneById(1);
154+
155+
if ($test === null) {
156+
throw new RuntimeException('Sorry, but no...');
157+
}
158+
159+
$test->doSomething();
160+
$test->doSomethingElse();
161+
}
162+
163+
public function findOneDynamicType2(): void
164+
{
165+
$test = $this->repository->findOneByNonexistent(1);
166+
}
167+
168+
public function countBy(): void
169+
{
170+
$test = $this->repository->countById(1);
171+
if ($test === 'foo') {
172+
173+
}
174+
$test = $this->repository->countByNonexistent('test');
175+
}
176+
}
177+
178+
/**
179+
* @ORM\Entity()
180+
*/
181+
class MyEntity
182+
{
183+
/**
184+
* @ORM\Id()
185+
* @ORM\GeneratedValue()
186+
* @ORM\Column(type="integer")
187+
*
188+
* @var int
189+
*/
190+
private $id;
191+
192+
public function doSomething(): void
193+
{
194+
}
195+
}
196+
197+
interface EntityInterface
198+
{
199+
200+
}
201+
202+
class GetRepositoryOnNonClasses
203+
{
204+
205+
public function doFoo(EntityManagerInterface $entityManager): void
206+
{
207+
$entityManager->getRepository(EntityInterface::class);
208+
}
209+
210+
public function doBar(EntityManagerInterface $entityManager): void
211+
{
212+
$entityManager->getRepository('nonexistentClass');
213+
}
214+
215+
}
216+
217+
abstract class BaseEntity
218+
{
219+
220+
/**
221+
* @return EntityRepository<static>
222+
*/
223+
public function getRepository(): EntityRepository
224+
{
225+
return $this->getEntityManager()->getRepository(static::class);
226+
}
227+
228+
abstract public function getEntityManager(): EntityManager;
229+
230+
}
231+
232+
class Bug180Repository extends EntityRepository
233+
{
234+
public const ALIAS = 'o';
235+
236+
237+
public function testingMethod(): int
238+
{
239+
$qb = $this->createQueryBuilder();
240+
241+
return (int) $qb->getQuery()->getSingleScalarResult();
242+
}
243+
}

‎tests/DoctrineIntegration/ORM/entity-manager.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
<?php declare(strict_types = 1);
22

33
use Doctrine\Common\Annotations\AnnotationReader;
4-
use Doctrine\Common\Cache\ArrayCache;
54
use Doctrine\ORM\Configuration;
65
use Doctrine\ORM\EntityManager;
76
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
7+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
8+
use Symfony\Component\Cache\DoctrineProvider;
89

910
$config = new Configuration();
1011
$config->setProxyDir(__DIR__);
1112
$config->setProxyNamespace('PHPstan\Doctrine\OrmProxies');
12-
$config->setMetadataCacheImpl(new ArrayCache());
13+
$config->setMetadataCacheImpl(new DoctrineProvider(new ArrayAdapter()));
1314

1415
$config->setMetadataDriverImpl(
1516
new AnnotationDriver(

‎tests/Rules/Doctrine/ORM/entity-manager.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
<?php declare(strict_types = 1);
22

33
use Doctrine\Common\Annotations\AnnotationReader;
4-
use Doctrine\Common\Cache\ArrayCache;
54
use Doctrine\ORM\Configuration;
65
use Doctrine\ORM\EntityManager;
76
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
7+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
8+
use Symfony\Component\Cache\DoctrineProvider;
89

910
$config = new Configuration();
1011
$config->setProxyDir(__DIR__);
1112
$config->setProxyNamespace('PHPstan\Doctrine\OrmProxies');
12-
$config->setMetadataCacheImpl(new ArrayCache());
13+
$config->setMetadataCacheImpl(new DoctrineProvider(new ArrayAdapter()));
1314

1415
$config->setMetadataDriverImpl(
1516
new AnnotationDriver(

0 commit comments

Comments
 (0)
Please sign in to comment.