Skip to content

Commit 4e5ae70

Browse files
refactor: Move UserPropertiesClassReflectionExtension to property directory and add testing. (#44)
1 parent d3acfa2 commit 4e5ae70

File tree

7 files changed

+200
-40
lines changed

7 files changed

+200
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Enh #41: Refactor `PHPDoc` comments for consistency and clarity (@terabytesoftw)
1212
- Bug #42: Move `ApplicationPropertiesClassReflectionExtension` to `property` directory and add testing (@terabytesoftw)
1313
- Enh #43: Add tests for session property availability in `Console` and `Web` `Applications` (@terabytesoftw)
14+
- Bug #44: Move `UserPropertiesClassReflectionExtension` to `property` directory and add testing (@terabytesoftw)
1415

1516
## 0.2.3 June 09, 2025
1617

composer-require-checker.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"PHPStan\\ShouldNotHappenException",
1515
"PHPStan\\TrinaryLogic",
1616
"PHPStan\\Type\\ArrayType",
17+
"PHPStan\\Type\\BooleanType",
1718
"PHPStan\\Type\\Constant\\ConstantArrayType",
1819
"PHPStan\\Type\\Constant\\ConstantBooleanType",
1920
"PHPStan\\Type\\Constant\\ConstantStringType",

extension.neon

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ services:
2626
-
2727
class: yii2\extensions\phpstan\property\BehaviorPropertiesClassReflectionExtension
2828
tags: [phpstan.broker.propertiesClassReflectionExtension]
29+
-
30+
class: yii2\extensions\phpstan\property\UserPropertiesClassReflectionExtension
31+
tags: [phpstan.broker.propertiesClassReflectionExtension]
2932
-
3033
class: yii2\extensions\phpstan\reflection\RequestMethodsClassReflectionExtension
3134
tags: [phpstan.broker.methodsClassReflectionExtension]
@@ -35,9 +38,6 @@ services:
3538
-
3639
class: yii2\extensions\phpstan\reflection\ResponsePropertiesClassReflectionExtension
3740
tags: [phpstan.broker.propertiesClassReflectionExtension]
38-
-
39-
class: yii2\extensions\phpstan\reflection\UserPropertiesClassReflectionExtension
40-
tags: [phpstan.broker.propertiesClassReflectionExtension]
4141
-
4242
class: yii2\extensions\phpstan\type\ActiveQueryDynamicMethodReturnTypeExtension
4343
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

src/reflection/UserPropertiesClassReflectionExtension.php renamed to src/property/UserPropertiesClassReflectionExtension.php

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace yii2\extensions\phpstan\reflection;
5+
namespace yii2\extensions\phpstan\property;
66

77
use PHPStan\Reflection\{
88
ClassReflection,
@@ -13,32 +13,35 @@
1313
};
1414
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
1515
use PHPStan\Reflection\Dummy\DummyPropertyReflection;
16-
use PHPStan\Type\Constant\ConstantBooleanType;
17-
use PHPStan\Type\{IntegerType, NullType, ObjectType, StringType, TypeCombinator};
16+
use PHPStan\Type\{BooleanType, IntegerType, NullType, ObjectType, StringType, TypeCombinator};
1817
use yii\web\User;
18+
use yii2\extensions\phpstan\reflection\ComponentPropertyReflection;
1919
use yii2\extensions\phpstan\ServiceMap;
2020

21+
use function in_array;
22+
use function is_string;
23+
2124
/**
22-
* Provides property reflection for a Yii user component in PHPStan analysis.
25+
* Provides property reflection for a Yii User component in PHPStan analysis.
2326
*
24-
* Integrates Yii's {@see User::identity] property and annotation-based property reflection into the user component
25-
* context, enabling accurate type inference and autocompletion for properties that are available on the user class.
27+
* Integrates a Yii User component {@see User::identity} property and annotation-based property reflection into the user
28+
* component context, enabling accurate type inference and autocompletion for properties that are available on the user
29+
* class.
2630
*
2731
* This extension allows PHPStan to recognize and reflect the {@see User::identity} property on the Yii user instance,
2832
* as well as properties defined natively or via annotations, even if they aren't declared as native properties on the
2933
* user class.
3034
*
3135
* The implementation delegates property lookups to annotation-based property extensions and native property reflection,
32-
* while providing a custom reflection for the dynamic {@see User::identity] property.
36+
* while providing a custom reflection for the dynamic {@see User::identity} property.
3337
*
3438
* Key features.
3539
* - Ensures compatibility with PHPStan strict analysis and autocompletion.
36-
* - Integrates annotation-based and native property reflection for the user component.
37-
* - Provides accurate type inference for the dynamic {@see User::identity] property.
38-
* - Supports dynamic and annotated property resolution for the user component.
40+
* - Integrates annotation-based and native property reflection for the {@see User} component.
41+
* - Provides accurate type inference for the dynamic {@see User::identity} property.
42+
* - Supports dynamic and annotated property resolution for the {@see User} component.
3943
*
40-
* @see AnnotationsPropertiesClassReflectionExtension for annotation support.
41-
* @see ComponentPropertyReflection for dynamic property reflection.
44+
* @see ComponentPropertyReflection for dynamic property reflection class.
4245
* @see PropertiesClassReflectionExtension for custom properties class reflection extension contract.
4346
*
4447
* @copyright Copyright (C) 2023 Terabytesoftw.
@@ -61,15 +64,16 @@ public function __construct(
6164
) {}
6265

6366
/**
64-
* Retrieves the property reflection for a given property on the Yii user component.
67+
* Retrieves the property reflection for a given property on the Yii User component.
6568
*
6669
* Resolves the property reflection for the specified property name by checking for the dynamic
67-
* {@see User::identity} property, native properties, and annotation-based properties on the Yii user instance.
70+
* {@see User::identity} property, native properties, and annotation-based properties on the Yii User instance.
6871
*
69-
* For the 'identity' property, it resolves the type based on the configured identityClass in the user component.
72+
* For the {@see User::identity} property, it resolves the type based on the configured {@see User::identityClass}
73+
* in the {@see User} component.
7074
*
71-
* @param ClassReflection $classReflection Class reflection instance for the Yii user component.
72-
* @param string $propertyName Name of the property to retrieve.
75+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
76+
* @param string $propertyName Name of the property to resolve.
7377
*
7478
* @throws MissingPropertyFromReflectionException if the property doesn't exist or can't be resolved.
7579
*
@@ -99,7 +103,7 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
99103
if ($propertyName === 'isGuest') {
100104
return new ComponentPropertyReflection(
101105
new DummyPropertyReflection($propertyName),
102-
new ConstantBooleanType(true),
106+
new BooleanType(),
103107
$classReflection,
104108
);
105109
}
@@ -121,15 +125,16 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
121125
}
122126

123127
/**
124-
* Determines whether the specified property exists on the Yii user component.
128+
* Determines whether the specified property exists on the Yii User component.
125129
*
126-
* Checks for the existence of a property on the user instance by considering native properties,
127-
* annotation-based properties, and the special 'identity' property.
130+
* Checks for the existence of a property on the user instance by considering native properties, annotation-based
131+
* properties, and the special {@see User::identity} property.
128132
*
129-
* @param ClassReflection $classReflection Class reflection instance for the Yii user component.
130-
* @param string $propertyName Name of the property to check for existence.
133+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
134+
* @param string $propertyName Name of the property to resolve.
131135
*
132-
* @return bool `true` if the property exists as a native, annotated, or identity property; `false` otherwise.
136+
* @return bool `true` if the property exists as a native, annotated, or {@see User::identity} property; `false`
137+
* otherwise.
133138
*/
134139
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
135140
{
@@ -146,12 +151,12 @@ public function hasProperty(ClassReflection $classReflection, string $propertyNa
146151
}
147152

148153
/**
149-
* Attempts to resolve the identity class from the user component configuration.
154+
* Attempts to resolve the {@see User::identityClass} from the user component configuration.
150155
*
151-
* This method tries to determine the identityClass configured for the user component
152-
* by looking at the service map's user component configuration.
156+
* This method tries to determine the {@see User::identityClass} configured for the user component by looking at the
157+
* service map's user component configuration.
153158
*
154-
* @return string|null The fully qualified identity class name, or null if not found.
159+
* @return string|null Fully qualified {@see User::identityClass} name, or `null` if not found.
155160
*/
156161
private function getIdentityClass(): string|null
157162
{
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace yii2\extensions\phpstan\tests\fixture\data\property;
6+
7+
use Yii;
8+
9+
use function PHPStan\Testing\assertType;
10+
11+
/**
12+
* Data provider for property reflection of Yii User component in PHPStan analysis.
13+
*
14+
* Validates type inference and return types for properties provided by the Yii User component, ensuring that PHPStan
15+
* correctly recognizes and infers types for available properties as if they were natively declared on the user object.
16+
*
17+
* These tests cover scenarios including direct property access, identity and guest checks, parameterized properties,
18+
* and shared property resolution, verifying that type assertions match the expected return types for each case.
19+
*
20+
* Key features.
21+
* - Coverage for identity, guest, and parameterized properties.
22+
* - Ensures compatibility with PHPStan property reflection for Yii user component.
23+
* - Type assertion for native and user-provided properties.
24+
* - Validates correct type inference for all supported property types.
25+
*
26+
* @copyright Copyright (C) 2023 Terabytesoftw.
27+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
28+
*/
29+
final class UserPropertiesClassReflectionType
30+
{
31+
public function testReturnBooleanFromIsGuestProperty(): void
32+
{
33+
assertType('bool', Yii::$app->user->isGuest);
34+
}
35+
36+
public function testReturnBooleanOrNullFromValidateAuthKeyMethod(): void
37+
{
38+
assertType('bool|null', Yii::$app->user->identity->validateAuthKey('123abc'));
39+
}
40+
41+
public function testReturnIdentityFromIdentityProperty(): void
42+
{
43+
assertType('yii2\extensions\phpstan\tests\stub\User', Yii::$app->user->identity);
44+
}
45+
46+
public function testReturnIdentityInterfaceOrNullFromFindIdentityByAccessTokenMethod(): void
47+
{
48+
assertType('yii\web\IdentityInterface|null', Yii::$app->user->identity::findIdentityByAccessToken('123abc'));
49+
}
50+
51+
public function testReturnIdentityInterfaceOrNullFromFindIdentityMethod(): void
52+
{
53+
assertType('yii\web\IdentityInterface|null', Yii::$app->user->identity::findIdentity(1));
54+
}
55+
56+
public function testReturnStringFromEmailProperty(): void
57+
{
58+
assertType('string', Yii::$app->user->identity->email);
59+
}
60+
61+
public function testReturnStringFromNameProperty(): void
62+
{
63+
assertType('string', Yii::$app->user->identity->name);
64+
}
65+
66+
public function testReturnStringFromReturnUrlProperty(): void
67+
{
68+
assertType('string', Yii::$app->user->returnUrl);
69+
}
70+
71+
public function testReturnStringOrNullFromGetAuthKeyMethod(): void
72+
{
73+
assertType('string|null', Yii::$app->user->identity->getAuthKey());
74+
}
75+
76+
public function testReturnUnionFromGetIdMethod(): void
77+
{
78+
assertType('int|string|null', Yii::$app->user->getId());
79+
}
80+
81+
public function testReturnUnionFromIdProperty(): void
82+
{
83+
assertType('int|string|null', Yii::$app->user->id);
84+
}
85+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace yii2\extensions\phpstan\tests\property;
6+
7+
use PHPStan\Testing\TypeInferenceTestCase;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
10+
/**
11+
* Test suite for type inference of property reflection in a Yii User component for PHPStan analysis.
12+
*
13+
* Validates that PHPStan correctly infers types for properties provided by the Yii User component, using fixture-based
14+
* assertions for direct property access, parameterized properties, and shared property resolution.
15+
*
16+
* The test class loads type assertions from a fixture file and delegates checks to the parent
17+
* {@see TypeInferenceTestCase}, ensuring that extension logic for {@see UserPropertiesClassReflectionExtension} is
18+
* robust and consistent with expected behavior.
19+
*
20+
* Key features.
21+
* - Ensures compatibility with PHPStan extension configuration.
22+
* - Loads and executes type assertions from a dedicated fixture file.
23+
* - Uses PHPUnit DataProvider for parameterized test execution.
24+
* - Validates type inference for native and user-provided properties.
25+
*
26+
* @copyright Copyright (C) 2023 Terabytesoftw.
27+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
28+
*/
29+
final class UserPropertiesClassReflectionExtensionTest extends TypeInferenceTestCase
30+
{
31+
/**
32+
* @return iterable<mixed>
33+
*/
34+
public static function dataFileAsserts(): iterable
35+
{
36+
$directory = dirname(__DIR__);
37+
38+
yield from self::gatherAssertTypes("{$directory}/fixture/data/property/UserPropertiesClassReflectionType.php");
39+
}
40+
41+
public static function getAdditionalConfigFiles(): array
42+
{
43+
return [dirname(__DIR__) . '/extension-tests.neon'];
44+
}
45+
46+
#[DataProvider('dataFileAsserts')]
47+
public function testFileAsserts(string $assertType, string $file, mixed ...$args): void
48+
{
49+
$this->assertFileAsserts($assertType, $file, ...$args);
50+
}
51+
}

tests/stub/User.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace yii2\extensions\phpstan\tests\stub;
66

77
use yii\db\ActiveRecord;
8+
use yii\web\IdentityInterface;
89

910
/**
1011
* User ActiveRecord model for testing property and rule definitions.
@@ -15,23 +16,39 @@
1516
* This class is used in PHPStan and static analysis scenarios to validate correct type inference for property access
1617
* and rule configuration in Yii Active Record models.
1718
*
18-
* Key features.
19-
* - Declares public properties {@see $id}, {@see $name}, and {@see $email} for type assertion tests.
20-
* - Defines {@see rules()} for property validation and type constraints.
21-
* - Implements {@see tableName()} for table mapping in test scenarios.
22-
*
2319
* @property int $id
2420
* @property string $name
2521
* @property string $email
2622
*
2723
* @copyright Copyright (C) 2023 Terabytesoftw.
2824
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
2925
*/
30-
class User extends ActiveRecord
26+
final class User extends ActiveRecord implements IdentityInterface
3127
{
32-
public int $id = 1;
33-
public string $name = 'John Doe';
34-
public string $email = 'john@example.com';
28+
public static function findIdentity($id): IdentityInterface|null
29+
{
30+
return \defined('YII_ENV_DEV') && YII_ENV_DEV ? new self() : null;
31+
}
32+
33+
public static function findIdentityByAccessToken($token, $type = null): IdentityInterface|null
34+
{
35+
return \defined('YII_ENV_DEV') && YII_ENV_DEV ? new self() : null;
36+
}
37+
38+
public function getId()
39+
{
40+
return YII_ENV_DEV ? 'dev-id' : 1;
41+
}
42+
43+
public function getAuthKey(): string|null
44+
{
45+
return YII_ENV_DEV ? 'dev-auth' : null;
46+
}
47+
48+
public function validateAuthKey($authKey): bool|null
49+
{
50+
return YII_ENV_DEV ? true : null;
51+
}
3552

3653
public static function tableName(): string
3754
{

0 commit comments

Comments
 (0)