Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 62a3b42

Browse files
acrobatondrejmirtes
authored andcommitted
Add ReadWritePropertiesExtension for Gedmo annotations/attributes
1 parent 529fa96 commit 62a3b42

10 files changed

+322
-0
lines changed

‎composer.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"doctrine/mongodb-odm": "^1.3 || ^2.1",
2626
"doctrine/orm": "^2.11.0",
2727
"doctrine/persistence": "^1.3.8 || ^2.2.1",
28+
"gedmo/doctrine-extensions": "^3.8",
2829
"nesbot/carbon": "^2.49",
2930
"nikic/php-parser": "^4.13.2",
3031
"php-parallel-lint/php-parallel-lint": "^1.2",

‎extension.neon‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,8 @@ services:
371371
class: PHPStan\Type\Doctrine\Collection\IsEmptyTypeSpecifyingExtension
372372
tags:
373373
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
374+
375+
-
376+
class: PHPStan\Rules\Gedmo\PropertiesExtension
377+
tags:
378+
- phpstan.properties.readWriteExtension

‎phpstan-baseline.neon‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ parameters:
55
count: 1
66
path: src/Doctrine/Mapping/ClassMetadataFactory.php
77

8+
-
9+
message: "#^Call to method getProperty\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#"
10+
count: 1
11+
path: src/Rules/Gedmo/PropertiesExtension.php
12+
813
-
914
message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#"
1015
count: 1
@@ -20,6 +25,16 @@ parameters:
2025
count: 1
2126
path: tests/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php
2227

28+
-
29+
message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#"
30+
count: 1
31+
path: tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php
32+
33+
-
34+
message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#"
35+
count: 1
36+
path: tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php
37+
2338
-
2439
message: "#^Accessing PHPStan\\\\Rules\\\\Properties\\\\MissingReadOnlyByPhpDocPropertyAssignRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#"
2540
count: 1
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Gedmo;
4+
5+
use Doctrine\Common\Annotations\AnnotationReader;
6+
use Gedmo\Mapping\Annotation as Gedmo;
7+
use PHPStan\Reflection\PropertyReflection;
8+
use PHPStan\Rules\Properties\ReadWritePropertiesExtension;
9+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
10+
use function class_exists;
11+
use function get_class;
12+
use function in_array;
13+
14+
class PropertiesExtension implements ReadWritePropertiesExtension
15+
{
16+
17+
private const GEDMO_WRITE_CLASSLIST = [
18+
Gedmo\Blameable::class,
19+
Gedmo\IpTraceable::class,
20+
Gedmo\Locale::class,
21+
Gedmo\Language::class,
22+
Gedmo\Slug::class,
23+
Gedmo\SortablePosition::class,
24+
Gedmo\Timestampable::class,
25+
Gedmo\TreeLeft::class,
26+
Gedmo\TreeLevel::class,
27+
Gedmo\TreeParent::class,
28+
Gedmo\TreePath::class,
29+
Gedmo\TreePathHash::class,
30+
Gedmo\TreeRight::class,
31+
Gedmo\TreeRoot::class,
32+
Gedmo\UploadableFileMimeType::class,
33+
Gedmo\UploadableFileName::class,
34+
Gedmo\UploadableFilePath::class,
35+
Gedmo\UploadableFileSize::class,
36+
];
37+
38+
private const GEDMO_READ_CLASSLIST = [
39+
Gedmo\Locale::class,
40+
Gedmo\Language::class,
41+
];
42+
43+
/** @var AnnotationReader|null */
44+
private $annotationReader;
45+
46+
/** @var ObjectMetadataResolver */
47+
private $objectMetadataResolver;
48+
49+
public function __construct(ObjectMetadataResolver $objectMetadataResolver)
50+
{
51+
$this->annotationReader = class_exists(AnnotationReader::class) ? new AnnotationReader() : null;
52+
$this->objectMetadataResolver = $objectMetadataResolver;
53+
}
54+
55+
public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
56+
{
57+
return $this->isGedmoAnnotationOrAttribute($property, $propertyName, self::GEDMO_READ_CLASSLIST);
58+
}
59+
60+
public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
61+
{
62+
return $this->isGedmoAnnotationOrAttribute($property, $propertyName, self::GEDMO_WRITE_CLASSLIST);
63+
}
64+
65+
public function isInitialized(PropertyReflection $property, string $propertyName): bool
66+
{
67+
return false;
68+
}
69+
70+
/**
71+
* @param array<class-string> $classList
72+
*/
73+
private function isGedmoAnnotationOrAttribute(PropertyReflection $property, string $propertyName, array $classList): bool
74+
{
75+
if ($this->annotationReader === null) {
76+
return false;
77+
}
78+
79+
$classReflection = $property->getDeclaringClass();
80+
if ($this->objectMetadataResolver->isTransient($classReflection->getName())) {
81+
return false;
82+
}
83+
84+
$propertyReflection = $classReflection->getNativeReflection()->getProperty($propertyName);
85+
86+
$annotations = $this->annotationReader->getPropertyAnnotations($propertyReflection);
87+
foreach ($annotations as $annotation) {
88+
if (in_array(get_class($annotation), $classList, true)) {
89+
return true;
90+
}
91+
}
92+
93+
$attributes = $propertyReflection->getAttributes();
94+
foreach ($attributes as $attribute) {
95+
if (in_array($attribute->getName(), $classList, true)) {
96+
return true;
97+
}
98+
}
99+
100+
return false;
101+
}
102+
103+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule;
6+
use PHPStan\Rules\Gedmo\PropertiesExtension;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Testing\RuleTestCase;
9+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
10+
use const PHP_VERSION_ID;
11+
12+
/**
13+
* @extends RuleTestCase<UnusedPrivatePropertyRule>
14+
*/
15+
class MissingGedmoByPhpDocPropertyAssignRuleTest extends RuleTestCase
16+
{
17+
18+
protected function getRule(): Rule
19+
{
20+
return self::getContainer()->getByType(UnusedPrivatePropertyRule::class);
21+
}
22+
23+
protected function getReadWritePropertiesExtensions(): array
24+
{
25+
return [
26+
new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')),
27+
];
28+
}
29+
30+
public static function getAdditionalConfigFiles(): array
31+
{
32+
return [__DIR__ . '/../../../extension.neon'];
33+
}
34+
35+
public function testRule(): void
36+
{
37+
if (PHP_VERSION_ID < 70400) {
38+
self::markTestSkipped('Test requires PHP 7.4.');
39+
}
40+
41+
$this->analyse([__DIR__ . '/data/gedmo-property-assign-phpdoc.php'], [
42+
// No errors expected
43+
]);
44+
}
45+
46+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Properties;
4+
5+
use Iterator;
6+
use PHPStan\Rules\DeadCode\UnusedPrivatePropertyRule;
7+
use PHPStan\Rules\Gedmo\PropertiesExtension;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
11+
use const PHP_VERSION_ID;
12+
13+
/**
14+
* @extends RuleTestCase<UnusedPrivatePropertyRule>
15+
*/
16+
class MissingGedmoPropertyAssignRuleTest extends RuleTestCase
17+
{
18+
19+
protected function getRule(): Rule
20+
{
21+
return self::getContainer()->getByType(UnusedPrivatePropertyRule::class);
22+
}
23+
24+
protected function getReadWritePropertiesExtensions(): array
25+
{
26+
return [
27+
new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')),
28+
];
29+
}
30+
31+
public static function getAdditionalConfigFiles(): array
32+
{
33+
return [__DIR__ . '/../../../extension.neon'];
34+
}
35+
36+
/**
37+
* @dataProvider ruleProvider
38+
* @param mixed[] $expectedErrors
39+
*/
40+
public function testRule(string $file, array $expectedErrors): void
41+
{
42+
if (PHP_VERSION_ID < 80100) {
43+
self::markTestSkipped('Test requires PHP 8.1.');
44+
}
45+
46+
$this->analyse([$file], $expectedErrors);
47+
}
48+
49+
/**
50+
* @return Iterator<mixed[]>
51+
*/
52+
public function ruleProvider(): Iterator
53+
{
54+
yield 'entity with gedmo attributes' => [
55+
__DIR__ . '/data/gedmo-property-assign.php',
56+
[
57+
// No errors expected
58+
],
59+
];
60+
61+
yield 'non-entity with gedmo attributes' => [
62+
__DIR__ . '/data/gedmo-property-assign-non-entity.php',
63+
[
64+
[
65+
'Property MissingGedmoWrittenPropertyAssign\NonEntityWithAGemdoLocaleField::$locale is unused.',
66+
10,
67+
'See: https://phpstan.org/developing-extensions/always-read-written-properties',
68+
],
69+
],
70+
];
71+
}
72+
73+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php // lint >= 8.1
2+
3+
namespace MissingGedmoWrittenPropertyAssign;
4+
5+
use Gedmo\Mapping\Annotation as Gedmo;
6+
7+
class NonEntityWithAGemdoLocaleField
8+
{
9+
#[Gedmo\Locale]
10+
private string $locale; // ok, locale is written and read by gedmo listeners
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php // lint >= 7.4
2+
3+
namespace MissingGedmoWrittenPropertyAssignPhpDoc;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
use Gedmo\Mapping\Annotation as Gedmo;
7+
8+
/**
9+
* @ORM\Entity
10+
*/
11+
class EntityWithAPhpDocGemdoLocaleField
12+
{
13+
/**
14+
* @Gedmo\Locale
15+
*/
16+
private string $locale; // ok, locale is written and read by gedmo listeners
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php // lint >= 8.1
2+
3+
namespace MissingGedmoWrittenPropertyAssign;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
use Gedmo\Mapping\Annotation as Gedmo;
7+
8+
#[ORM\Entity]
9+
class EntityWithAGemdoLocaleField
10+
{
11+
#[Gedmo\Locale]
12+
private string $locale; // ok, locale is written and read by gedmo listeners
13+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Doctrine\Common\Annotations\AnnotationReader;
4+
use Doctrine\ORM\Configuration;
5+
use Doctrine\ORM\EntityManager;
6+
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
7+
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
8+
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
9+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
10+
use Symfony\Component\Cache\DoctrineProvider;
11+
12+
$config = new Configuration();
13+
$config->setProxyDir(__DIR__);
14+
$config->setProxyNamespace('PHPstan\Doctrine\OrmProxies');
15+
$config->setMetadataCacheImpl(new DoctrineProvider(new ArrayAdapter()));
16+
17+
$metadataDriver = new MappingDriverChain();
18+
$metadataDriver->addDriver(new AnnotationDriver(
19+
new AnnotationReader(),
20+
[__DIR__ . '/data']
21+
), 'PHPStan\\Rules\\Doctrine\\ORM\\');
22+
23+
if (PHP_VERSION_ID >= 80100) {
24+
$metadataDriver->addDriver(
25+
new AttributeDriver([__DIR__ . '/data']),
26+
'PHPStan\\Rules\\Doctrine\\ORMAttributes\\'
27+
);
28+
}
29+
30+
$config->setMetadataDriverImpl($metadataDriver);
31+
32+
return EntityManager::create(
33+
[
34+
'driver' => 'pdo_sqlite',
35+
'memory' => true,
36+
],
37+
$config
38+
);

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /