From f42828ad684e026054c3d94161d89870150bc3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=BD=C3=A1=C4=8Dek?= Date: Wed, 20 Mar 2024 14:16:13 +0100 Subject: [PATCH] Append Doctrine proxy class names to list of forbidden class names to disallow direct use of Doctrine proxy classes. --- composer.json | 2 +- extension.neon | 6 +++ phpstan-baseline.neon | 5 ++ rules.neon | 4 ++ ...trineProxyForbiddenClassNamesExtension.php | 37 +++++++++++++++ ...eProxyForbiddenClassNamesExtensionTest.php | 47 +++++++++++++++++++ tests/Classes/data/forbidden-class-name.php | 20 ++++++++ tests/Classes/entity-manager.php | 45 ++++++++++++++++++ tests/Classes/phpstan.neon | 6 +++ 9 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/Classes/DoctrineProxyForbiddenClassNamesExtension.php create mode 100644 tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php create mode 100644 tests/Classes/data/forbidden-class-name.php create mode 100644 tests/Classes/entity-manager.php create mode 100644 tests/Classes/phpstan.neon diff --git a/composer.json b/composer.json index 6ef8f79b..882b9ef9 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.10.63" + "phpstan/phpstan": "^1.10.64" }, "conflict": { "doctrine/collections": "<1.0", diff --git a/extension.neon b/extension.neon index 2d7e1cb0..c83e8c0c 100644 --- a/extension.neon +++ b/extension.neon @@ -426,3 +426,9 @@ services: class: PHPStan\Type\Doctrine\EntityManagerInterfaceThrowTypeExtension tags: - phpstan.dynamicMethodThrowTypeExtension + + # Forbidden class names extension + - + class: PHPStan\Classes\DoctrineProxyForbiddenClassNamesExtension + tags: + - phpstan.forbiddenClassNamesExtension diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f5f73b8a..cc1e2048 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,6 +25,11 @@ parameters: count: 1 path: src/Stubs/Doctrine/StubFilesExtensionLoader.php + - + message: "#^Accessing PHPStan\\\\Rules\\\\Classes\\\\InstantiationRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + count: 1 + path: tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php + - message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 diff --git a/rules.neon b/rules.neon index 7545b956..feb41184 100644 --- a/rules.neon +++ b/rules.neon @@ -59,3 +59,7 @@ services: - phpstan.rules.rule - class: PHPStan\Rules\Doctrine\ORM\EntityConstructorNotFinalRule + - + class: PHPStan\Classes\DoctrineProxyForbiddenClassNamesExtension + tags: + - phpstan.forbiddenClassNamesExtension diff --git a/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php b/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php new file mode 100644 index 00000000..1ff7f4f6 --- /dev/null +++ b/src/Classes/DoctrineProxyForbiddenClassNamesExtension.php @@ -0,0 +1,37 @@ +objectMetadataResolver = $objectMetadataResolver; + } + + public function getClassPrefixes(): array + { + $objectManager = $this->objectMetadataResolver->getObjectManager(); + if ($objectManager === null) { + return []; + } + + $entityManagerInterface = 'Doctrine\ORM\EntityManagerInterface'; + + if (!$objectManager instanceof $entityManagerInterface) { + return []; + } + + return [ + 'Doctrine' => $objectManager->getConfiguration()->getProxyNamespace() . '\\' . Proxy::MARKER, + ]; + } + +} diff --git a/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php b/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php new file mode 100644 index 00000000..f1f7dd32 --- /dev/null +++ b/tests/Classes/DoctrineProxyForbiddenClassNamesExtensionTest.php @@ -0,0 +1,47 @@ + + */ +class DoctrineProxyForbiddenClassNamesExtensionTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(InstantiationRule::class); + } + + public function testForbiddenClassNameExtension(): void + { + $this->analyse( + [__DIR__ . '/data/forbidden-class-name.php'], + [ + [ + 'Referencing prefixed Doctrine class: App\GeneratedProxy\__CG__\App\TestDoctrineEntity.', + 19, + 'This is most likely unintentional. Did you mean to type \App\TestDoctrineEntity?', + ], + [ + 'Referencing prefixed PHPStan class: _PHPStan_15755dag8c\TestPhpStanEntity.', + 20, + 'This is most likely unintentional. Did you mean to type \TestPhpStanEntity?', + ], + ] + ); + } + + public static function getAdditionalConfigFiles(): array + { + return array_merge(parent::getAdditionalConfigFiles(), [ + __DIR__ . '/phpstan.neon', + ]); + } + +} diff --git a/tests/Classes/data/forbidden-class-name.php b/tests/Classes/data/forbidden-class-name.php new file mode 100644 index 00000000..ff0026df --- /dev/null +++ b/tests/Classes/data/forbidden-class-name.php @@ -0,0 +1,20 @@ +setProxyDir(__DIR__); +$config->setProxyNamespace('App\GeneratedProxy'); +$config->setMetadataCache(new ArrayCachePool()); + +$metadataDriver = new MappingDriverChain(); +$metadataDriver->addDriver(new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/data'] +), 'PHPStan\\Rules\\Doctrine\\ORM\\'); + +if (PHP_VERSION_ID >= 80100) { + $metadataDriver->addDriver( + new AttributeDriver([__DIR__ . '/data-attributes']), + 'PHPStan\\Rules\\Doctrine\\ORMAttributes\\' + ); +} + +$config->setMetadataDriverImpl($metadataDriver); + +Type::overrideType( + 'date', + DateTimeImmutableType::class +); + +return new EntityManager( + DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ]), + $config +); diff --git a/tests/Classes/phpstan.neon b/tests/Classes/phpstan.neon new file mode 100644 index 00000000..4de41cb9 --- /dev/null +++ b/tests/Classes/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ../../extension.neon + +parameters: + doctrine: + objectManagerLoader: entity-manager.php