Skip to content

Commit f528c8f

Browse files
refactor: Move ApplicationPropertiesClassReflectionExtension to property directory and add testing. (#42)
1 parent f9a5422 commit f528c8f

9 files changed

+625
-151
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Bug #39: Resolve `Container::get()` type inference for unconfigured classes in config (`ServiceMap`) (@terabytesoftw)
1010
- Enh #40: Enhance `PHPStan` analysis for `Behavior` type inference and testing (@terabytesoftw)
1111
- Enh #41: Refactor `PHPDoc` comments for consistency and clarity (@terabytesoftw)
12+
- Bug #42: Move `ApplicationPropertiesClassReflectionExtension` to `property` directory and add testing (@terabytesoftw)
1213

1314
## 0.2.3 June 09, 2025
1415

extension.neon

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ services:
2121
class: yii2\extensions\phpstan\method\BehaviorMethodsClassReflectionExtension
2222
tags: [phpstan.broker.methodsClassReflectionExtension]
2323
-
24-
class: yii2\extensions\phpstan\reflection\ApplicationPropertiesClassReflectionExtension
24+
class: yii2\extensions\phpstan\property\ApplicationPropertiesClassReflectionExtension
25+
tags: [phpstan.broker.propertiesClassReflectionExtension]
26+
-
27+
class: yii2\extensions\phpstan\property\BehaviorPropertiesClassReflectionExtension
2528
tags: [phpstan.broker.propertiesClassReflectionExtension]
2629
-
2730
class: yii2\extensions\phpstan\reflection\RequestMethodsClassReflectionExtension
@@ -35,9 +38,6 @@ services:
3538
-
3639
class: yii2\extensions\phpstan\reflection\UserPropertiesClassReflectionExtension
3740
tags: [phpstan.broker.propertiesClassReflectionExtension]
38-
-
39-
class: yii2\extensions\phpstan\property\BehaviorPropertiesClassReflectionExtension
40-
tags: [phpstan.broker.propertiesClassReflectionExtension]
4141
-
4242
class: yii2\extensions\phpstan\type\ActiveQueryDynamicMethodReturnTypeExtension
4343
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace yii2\extensions\phpstan\property;
6+
7+
use PHPStan\Reflection\{
8+
ClassReflection,
9+
MissingPropertyFromReflectionException,
10+
PropertiesClassReflectionExtension,
11+
PropertyReflection,
12+
ReflectionProvider,
13+
};
14+
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
15+
use PHPStan\Reflection\Dummy\DummyPropertyReflection;
16+
use PHPStan\Type\{MixedType, ObjectType};
17+
use yii\base\Application as BaseApplication;
18+
use yii\console\Application as ConsoleApplication;
19+
use yii\web\Application as WebApplication;
20+
use yii2\extensions\phpstan\reflection\ComponentPropertyReflection;
21+
use yii2\extensions\phpstan\ServiceMap;
22+
23+
use function in_array;
24+
25+
/**
26+
* Provides property reflection for a Yii Application component in PHPStan analysis.
27+
*
28+
* Integrates Yii DI container service resolution and service map with PHPStan property reflection system, enabling
29+
* accurate type inference and autocompletion for dynamic application components and services.
30+
*
31+
* This extension allows PHPStan to recognize properties on the Yii Application instance that are defined via
32+
* configuration, dependency injection, or service mapping, even if they aren't declared as native properties.
33+
*
34+
* The implementation delegates to annotation-based property extensions, native property reflection, and a custom
35+
* service map for component resolution.
36+
*
37+
* This ensures that all valid application properties whether declared, annotated, or registered as components are
38+
* available for static analysis and IDE support.
39+
*
40+
* Key features.
41+
* - Enables accurate type inference for injected services and components.
42+
* - Ensures compatibility with PHPStan strict analysis and autocompletion.
43+
* - Handles base, web, and console application contexts.
44+
* - Integrates annotation-based and native property reflection.
45+
* - Supports dynamic Yii Application properties via service map lookup.
46+
*
47+
* @see BaseApplication for Yii Base Application class.
48+
* @see ConsoleApplication for Yii Console Application class.
49+
* @see PropertiesClassReflectionExtension for custom properties class reflection extension contract.
50+
* @see WebApplication for Yii Web Application class.
51+
*
52+
* @copyright Copyright (C) 2023 Terabytesoftw.
53+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
54+
*/
55+
final class ApplicationPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension
56+
{
57+
/**
58+
* Creates a new instance of the {@see ApplicationPropertiesClassReflectionExtension} class.
59+
*
60+
* @param AnnotationsPropertiesClassReflectionExtension $annotationsProperties Extension for handling
61+
* annotation-based properties.
62+
* @param ReflectionProvider $reflectionProvider Reflection provider for class and property lookups.
63+
* @param ServiceMap $serviceMap Service map for resolving component classes by ID.
64+
*/
65+
public function __construct(
66+
private readonly AnnotationsPropertiesClassReflectionExtension $annotationsProperties,
67+
private readonly ReflectionProvider $reflectionProvider,
68+
private readonly ServiceMap $serviceMap,
69+
) {}
70+
71+
/**
72+
* Retrieves the property reflection for a given property on the Yii Application class or its components.
73+
*
74+
* Resolves the property reflection for the specified property name by checking for dynamic components, native
75+
* properties, and annotation-based properties on the Yii Application instance.
76+
*
77+
* This method ensures that properties defined via configuration, dependency injection, or service mapping are
78+
* accessible for static analysis and IDE support.
79+
*
80+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
81+
* @param string $propertyName Name of the property to resolve.
82+
*
83+
* @throws MissingPropertyFromReflectionException if the property doesn't exist or can't be resolved.
84+
*
85+
* @return PropertyReflection Property reflection instance for the specified property.
86+
*/
87+
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
88+
{
89+
$normalizedClassReflection = $this->normalizeClassReflection($classReflection);
90+
91+
if ($normalizedClassReflection->hasNativeProperty($propertyName)) {
92+
return $normalizedClassReflection->getNativeProperty($propertyName);
93+
}
94+
95+
if ($this->annotationsProperties->hasProperty($normalizedClassReflection, $propertyName)) {
96+
return $this->annotationsProperties->getProperty($normalizedClassReflection, $propertyName);
97+
}
98+
99+
if (null !== $componentClass = $this->serviceMap->getComponentClassById($propertyName)) {
100+
return new ComponentPropertyReflection(
101+
new DummyPropertyReflection($propertyName),
102+
new ObjectType($componentClass),
103+
$normalizedClassReflection,
104+
);
105+
}
106+
107+
return new ComponentPropertyReflection(
108+
new DummyPropertyReflection($propertyName),
109+
new MixedType(),
110+
$normalizedClassReflection,
111+
);
112+
}
113+
114+
/**
115+
* Determines whether the specified property exists on the Yii Application class or its components.
116+
*
117+
* Checks for the existence of a property on the Yii Application instance by considering native properties,
118+
* annotation-based properties, and dynamic components registered via the service map.
119+
*
120+
* This method ensures compatibility with base, web, and console application contexts, enabling accurate property
121+
* reflection for static analysis and IDE autocompletion.
122+
*
123+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
124+
* @param string $propertyName Name of the property to resolve.
125+
*
126+
* @return bool `true` if the property exists as a native, annotated, or component property; `false` otherwise.
127+
*/
128+
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
129+
{
130+
if ($this->isApplicationClass($classReflection) === false) {
131+
return false;
132+
}
133+
134+
$normalizedClassReflection = $this->normalizeClassReflection($classReflection);
135+
136+
return $normalizedClassReflection->hasNativeProperty($propertyName)
137+
|| $this->annotationsProperties->hasProperty($normalizedClassReflection, $propertyName)
138+
|| $this->serviceMap->getComponentClassById($propertyName) !== null;
139+
}
140+
141+
/**
142+
* Determines if the provided class reflection corresponds to a Yii Application class or its subclass.
143+
*
144+
* This method checks whether the given {@see ClassReflection} instance represents the base Yii Application, the
145+
* console or web application, or any subclass.
146+
*
147+
* This check is essential for restricting property reflection logic to valid Yii Application contexts, ensuring
148+
* that dynamic property resolution is only applied where appropriate.
149+
*
150+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
151+
*
152+
* @return bool `true` if the class is a Yii Application or subclass; `false` otherwise.
153+
*/
154+
private function isApplicationClass(ClassReflection $classReflection): bool
155+
{
156+
$className = $classReflection->getName();
157+
158+
if (in_array($className, [BaseApplication::class, ConsoleApplication::class, WebApplication::class], true)) {
159+
return true;
160+
}
161+
162+
if ($this->reflectionProvider->hasClass(BaseApplication::class)) {
163+
return $classReflection->isSubclassOfClass($this->reflectionProvider->getClass(BaseApplication::class));
164+
}
165+
166+
return false;
167+
}
168+
169+
/**
170+
* Normalizes the class reflection for Yii Application subclasses to ensure consistent property resolution.
171+
*
172+
* This method checks if the provided {@see ClassReflection} instance represents the base Yii Application, console
173+
* application, web application, or any subclass.
174+
*
175+
* The normalization process is essential for property reflection logic, as it ensures that dynamic property
176+
* resolution and component lookup are only applied to valid Yii Application contexts.
177+
*
178+
* @param ClassReflection $classReflection Reflection of the class being analyzed.
179+
*
180+
* @return ClassReflection Normalized class reflection for the application.
181+
*/
182+
private function normalizeClassReflection(ClassReflection $classReflection): ClassReflection
183+
{
184+
$className = $classReflection->getName();
185+
186+
if (in_array($className, [BaseApplication::class, ConsoleApplication::class, WebApplication::class], true)) {
187+
return $classReflection;
188+
}
189+
190+
if ($this->reflectionProvider->hasClass(ConsoleApplication::class)) {
191+
$consoleAppReflection = $this->reflectionProvider->getClass(ConsoleApplication::class);
192+
193+
if ($classReflection->isSubclassOfClass($consoleAppReflection)) {
194+
return $classReflection;
195+
}
196+
}
197+
198+
if ($this->reflectionProvider->hasClass(WebApplication::class)) {
199+
$webAppReflection = $this->reflectionProvider->getClass(WebApplication::class);
200+
201+
if ($classReflection->isSubclassOfClass($webAppReflection)) {
202+
return $classReflection;
203+
}
204+
}
205+
206+
return $classReflection;
207+
}
208+
}

src/reflection/ApplicationPropertiesClassReflectionExtension.php

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)